Hello! Welcome to the next lesson in our "Container Security Hardening" module.
In our last session, we focused on hardening the container's filesystem by making it read-only, which prevents attackers from modifying your application or installing tools. Today, we shift our focus from the filesystem to the sensitive data your application needs to operate, such as API keys, database credentials, and certificates.
This lesson directly addresses a critical aspect of your role as a DevOps Engineer: managing secrets. Our learning outcome is to differentiate between Docker secrets and environment variables for managing sensitive data. Mishandling this data is one of the most common and damaging security vulnerabilities, so mastering this is essential for building production-ready systems.
1. The Standard: Configuration with Environment Variables
A core principle in modern application development, popularized by the 12-Factor App methodology, is to store configuration in the environment, completely separate from the application code. This allows you to deploy the same container image across different environments (dev, staging, prod) simply by changing its configuration.
Docker embraces this principle, making it easy to pass environment variables to containers.
Docker Environment Configs, Variables, and Entrypoints
To start, let's watch a brief clip from Docker Captain Bret Fisher. He explains this core concept of using environment variables for configuration, which sets the stage for our discussion.
Watch the first five minutes of the video. Focus on the '12-Factor App' principle of strict separation between config and code, and why environment variables are a universal way to achieve this.
While environment variables are perfect for non-sensitive data like port numbers or feature flags, they become a significant liability when used for secrets.
2. The Problem: Why Environment Variables Are Unsafe for Secrets
Using environment variables for secrets is a widespread but dangerous practice. The fundamental issue is that they are stored in plain text and can be exposed in numerous ways.
Let's return to Bret Fisher's video, where he explicitly discusses what should not go into environment variables.
Docker Environment Configs, Variables, and Entrypoints
This clip directly addresses the security risks associated with using environment variables for sensitive data.
Watch from 15:32 to 18:10. Pay close attention to the reasons why secrets in environment variables are risky, such as leakage into logs, debugging tools, or error messages.
To summarize and reinforce these points, let's review a short article.
Guide to Secure Container Secrets Management
The article 'Guide to Secure Container Secrets Management' from Wiz provides a clear definition of secrets and the risks of using environment variables.
Read the section 'What are Docker secrets and why do they matter?'. This text clearly summarizes the risks of hard-coding credentials or passing them as environment variables.
The key risks are:
- Inspection: Anyone with access to the Docker host can run
docker inspect <container_id>and see all environment variables in plain text. - Process Snooping: On the host, a simple
ps auxcommand can sometimes reveal environment variables of running processes. - Log Leakage: Applications often log errors that include a full environment dump for debugging, inadvertently writing secrets to logs.
- Linked Containers: Child processes and linked containers can inherit environment variables, exposing secrets to services that don't need them.
- Source Control: Storing secrets in
.envfiles ordocker-compose.ymlfiles which are then committed to version control is a very common mistake.
3. The Solution: Docker Secrets
To address these risks, Docker provides a built-in mechanism for managing secrets securely: Docker Secrets. This feature is available for services running in Docker Swarm or managed by Docker Compose.
How Docker Secrets Work
Instead of injecting secrets as plain text, Docker Secrets works differently.
Manage sensitive data with Docker secrets
The official Docker documentation provides the best explanation of the underlying mechanics of Docker Secrets.
Read the section 'How Docker manages secrets'. Focus on how secrets are encrypted at rest (in the Raft log) and in transit, and how they are mounted into the container's memory.
As the documentation describes, a secret is:
- Encrypted at Rest: Stored in the encrypted Raft log on swarm manager nodes.
- Encrypted in Transit: Sent to authorized containers over a secure TLS connection.
- Stored in Memory: Mounted into the container as a file in a
tmpfsin-memory filesystem (by default at/run/secrets/<secret_name>). This is the sametmpfsconcept we saw in the last lesson for read-only filesystems. Since it's in memory, it's never written to the container's disk and vanishes when the container stops.
Docker Secrets vs. Environment Variables: The Differentiation
Let's clearly differentiate the two approaches.
Guide to Secure Container Secrets Management
The Wiz article provides an excellent side-by-side comparison.
Read the section 'Security benefits of Docker secrets over traditional approaches' for a direct comparison covering encryption, lifespan, and management.
| Feature | Environment Variables | Docker Secrets |
|---|---|---|
| Storage | Plain text, visible via docker inspect |
Encrypted at rest, not visible via inspect |
| Access | Injected into the container's environment | Mounted as a file in an in-memory tmpfs |
| Lifespan | Persists as long as the container exists | Exists only in memory while the container runs |
| Scope | Available to Swarm, Compose, and standalone docker run |
Available only to Swarm and Compose services |
| Management | Decentralized (CLI flags, Compose files, .env files) |
Centralized management via Docker API (docker secret commands) |
4. Implementing Docker Secrets
To use Docker Secrets, your application needs to be able to read its configuration from files instead of just environment variables. Many official images support this with a special convention.
Manage sensitive data with Docker secrets
Let's review the official Docker documentation again to see how to implement this in practice.
First, read 'Build support for Docker Secrets into your images' to understand the _FILE suffix pattern. Then, review the example in 'Use Secrets in Compose' to see the full docker-compose.yml syntax.
The common pattern is:
- Define your secrets in a top-level
secrets:block in yourdocker-compose.yml, pointing to external files. This keeps the secret values out of your version-controlled Compose file. - Grant a service access to a secret using the
secrets:block within that service's definition. - The application image is designed to look for an environment variable ending in
_FILE(e.g.,MYSQL_PASSWORD_FILE). If it finds this, it reads the content of the file specified by that variable instead of looking for theMYSQL_PASSWORDvariable. - You set this
_FILEvariable in yourdocker-compose.ymlto point to the file path where Docker mounts the secret (e.g.,/run/secrets/db_password).
Test your understanding!
You have a service in a docker-compose.yml that uses a database password stored insecurely in an environment variable:
services:
api:
image: my-api:1.0
environment:
- DB_PASSWORD=supersecret123
Assume the my-api:1.0 image supports the DB_PASSWORD_FILE convention. Refactor this service to use Docker Secrets. You'll need to create a file for the secret and modify the docker-compose.yml.
Show answer
-
Create the secret file: First, create a file to hold the secret. For example,
db_password.txt.echo "supersecret123" > db_password.txt -
Refactor
docker-compose.yml: Modify the file to define and use the secret.version: '3.8' services: api: image: my-api:1.0 environment: # Set the _FILE variable to point to the secret's mount path - DB_PASSWORD_FILE=/run/secrets/api_db_password secrets: # Grant this service access to the secret - api_db_password # Define the secret at the top level, referencing the external file secrets: api_db_password: file: ./db_password.txt
5. Bonus: Secrets at Build Time
So far, we've discussed runtime secrets. But what if you need a secret during the docker build process, for example, to git clone a private repository or download packages from a private registry? A common mistake is to use ARG.
docker: secrets at build time! (intermediate) anthony explains #544
This video from 'anthonywritescode' perfectly explains why using ARG is a security risk and demonstrates the modern, secure solution using Docker BuildKit.
Watch from 00:41 to 06:11. The first part shows how ARG can leak secrets into the image history. The second part introduces the --mount=type=secret flag, which securely mounts a secret for a single RUN command without baking it into any image layer.
Using --mount=type=secret is a critical technique for secure CI/CD pipelines, ensuring your build-time credentials don't end up embedded in your final production image.
Conclusion
You now have a clear understanding of the crucial differences between environment variables and Docker Secrets for managing sensitive data. This knowledge is fundamental to building secure, production-grade containerized applications.
Key Takeaways:
- Environment variables are for non-sensitive configuration only. They are insecure for secrets as they can be easily leaked through logs, inspection, and process lists.
- Docker Secrets are the secure, built-in mechanism for managing runtime secrets in Docker Compose and Swarm. They are encrypted, managed centrally, and mounted into a container's memory as a temporary file.
- The
_FILEsuffix is a common convention used by container images to read a secret's value from its mounted file path. - For build-time secrets, never use
ARG. Instead, use Docker BuildKit's--mount=type=secretfeature to temporarily provide credentials during the build process without embedding them in the image.
In our final lesson for this security module, we will investigate another high-stakes topic: analyzing the security implications of mounting the Docker socket in a container. This is a powerful but potentially dangerous pattern that you will frequently encounter in the wild.