Skip to content
Laviyx Learn

Try

Docker and Kubernetes course menu

Docker Environment Variables, .env Files and Secrets

Configure containers with -e and --env-file, understand ENV and ARG, keep secrets out of images, and use mounted secret files.

Intermediate5 min readUpdated
On this page

The same image should run in development, testing and production, so settings must live outside it. The usual way is environment variables.

Set variables#

-e sets a variable for one container:

Terminal
docker run --rm -e APP_ENV=production -e PORT=9000 alpine:3.24 sh -c 'echo "$APP_ENV on port $PORT"'
Output
production on port 9000

Use a file#

--env-file loads many at once. The format is one KEY=value per line:

Terminal
cat > .env <<'EOF'
APP_ENV=staging
LOG_LEVEL=debug
# Comments are allowed
EOF
docker run --rm --env-file .env alpine:3.24 sh -c 'env | grep -E "APP_ENV|LOG_LEVEL" | sort'
Output
APP_ENV=staging
LOG_LEVEL=debug

Add .env to both .gitignore and .dockerignore. Commit a .env.example with fake values, so that others know which settings exist.

Defaults in the image#

ENV in a Dockerfile sets a default, and -e overrides it:

Terminal
cat > Dockerfile <<'EOF'
FROM alpine:3.24
ENV COLOUR=blue
CMD ["sh", "-c", "echo $COLOUR"]
EOF
docker build -q -t colour . > /dev/null
docker run --rm colour
docker run --rm -e COLOUR=green colour
Output
blue
green
ARGENV
Available during buildYesYes
Available in the containerNoYes
Set with--build-arg-e at run time

Do not bake in secrets#

Anything in a Dockerfile can be read from the image, even after a later step removes it:

Terminal
cat > Dockerfile <<'EOF'
FROM alpine:3.24
ARG API_KEY
RUN echo "building"
ENV TOKEN=abc123
EOF
docker build -q --build-arg API_KEY=super-secret -t leaky . > /dev/null
docker history --no-trunc --format '{{.CreatedBy}}' leaky | grep -o 'API_KEY=[a-z-]*'
docker inspect --format '{{.Config.Env}}' leaky
Output
API_KEY=super-secret
API_KEY=super-secret
[PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin TOKEN=abc123]

Both values can be read by anyone who has the image.

Environment variables are not perfect#

Run-time variables are better, but they can still be seen by anyone who can inspect the container:

Terminal
docker run -d --name app -e DB_PASSWORD=hunter2 alpine:3.24 sleep 30 > /dev/null
docker inspect --format '{{.Config.Env}}' app | grep -o 'DB_PASSWORD=[a-z0-9]*'
docker rm -f app > /dev/null
Output
DB_PASSWORD=hunter2

Secret files#

A better way is to mount a secret as a read-only file, which the app reads at start-up. Many official images support a _FILE variable for this:

Terminal
mkdir secrets
echo "s3cret-value" > secrets/db_password
chmod 600 secrets/db_password
docker run --rm -v "$PWD/secrets/db_password":/run/secrets/db_password:ro alpine:3.24 sh -c 'echo "Password length: $(wc -c < /run/secrets/db_password)"'
Output
Password length: 13

For example, postgres accepts POSTGRES_PASSWORD_FILE=/run/secrets/db_password. Compose has a secrets section that does this mounting for you. In Kubernetes, you use Secrets. In the cloud, use a service such as AWS Secrets Manager.

Secrets during a build#

If a build needs a token, for example to install private packages, BuildKit secrets keep it out of the layers:

DOCKERFILE
RUN --mount=type=secret,id=npm_token sh -c 'NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci'
Terminal
docker build --secret id=npm_token,src=.npm_token -t app .

Common mistakes#

Practice#

Practice exercise

Create prod.env with MODE=prod and WORKERS=4, and run an Alpine container that uses it, overriding WORKERS to 8 with -e. Print both values.

Show solution
Terminal
printf 'MODE=prod\nWORKERS=4\n' > prod.env
docker run --rm --env-file prod.env -e WORKERS=8 alpine:3.24 sh -c 'echo "$MODE $WORKERS"'
Output
prod 8

Key takeaway#

Next: Getting Started with Compose.

Finished this lesson?

Some links are affiliate links. If you buy through them, Laviyx Learn may earn a commission at no extra cost to you. As an Amazon Associate, we earn from qualifying purchases.