Environment Variables
ជំពូកនេះមិនទាន់ត្រូវបានបកប្រែជាភាសាខ្មែរនៅឡើយទេ — សូមអានជាភាសាអង់គ្លេសខាងក្រោម។
Table of Contents
Every example so far has touched environment variables in passing—ENV in a Dockerfile (Part 6), environment: in Compose (Part 10), DB_HOST/REDIS_HOST in the multi-container project (Part 11). This section takes a closer look at how to manage them properly: loading .env files, passing variables at runtime, handling secrets safely, and configuring different environments.
1. .env Files
A .env file stores key-value pairs outside your code and Dockerfiles, so configuration can change without rebuilding an image.
# .env
DB_HOST=db
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=app_db
# Load a .env file into a single container at runtime
docker run --env-file .env my-api:v1
docker compose automatically loads a .env file from the project root (no flag needed) and uses it in two ways:
- To substitute
${VARIABLE}placeholders insidedocker-compose.ymlitself. - To populate a service's
environment:block, as seen throughout Part 11.
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
2. Passing Variables
Beyond .env files, variables can be passed directly at the command line—useful for one-off overrides or CI pipelines.
# Pass a single variable with -e
docker run -e NODE_ENV=production -e PORT=3000 my-api:v1
# Pass multiple variables
docker run \
-e DB_HOST=db \
-e DB_PORT=5432 \
my-api:v1
# Forward a variable already set in your shell (no value needed)
export API_KEY=abc123
docker run -e API_KEY my-api:v1
In Compose, the same environment: key accepts either a map or a list, and can reference shell/.env variables directly:
services:
api:
image: my-api:v1
environment:
- NODE_ENV=production
- API_KEY=${API_KEY}
Precedence (highest to lowest) when the same variable is set multiple ways: shell environment > -e/environment: in the Compose file > .env file > Dockerfile ENV default.
3. Secrets
Passwords, API keys, and tokens need extra care—.env files and -e flags are visible in docker inspect, shell history, and process listings, which is acceptable for local development but not for production secrets.
-
Never bake secrets into an image: A value passed as a Dockerfile
ARGorRUN echo $SECRET > fileis permanently embedded in the image's build history/layers, retrievable by anyone with the image—even after removing the file in a later layer.# BAD: secret leaks into image layer history ARG DB_PASSWORD RUN echo "$DB_PASSWORD" >> /app/config # BETTER: inject at runtime instead, never at build time -
.gitignoreyour.envfiles: Commit a.env.examplewith placeholder values (as done inmulti-container-project/.env.example) and add.envto.gitignore—never commit real credentials. -
Docker Secrets (Swarm/Compose): For a more secure mechanism than plain environment variables, Docker can mount a secret as a file inside the container instead of exposing it as an env var:
services: db: image: postgres:16-alpine secrets: - db_password environment: POSTGRES_PASSWORD_FILE: /run/secrets/db_password secrets: db_password: file: ./secrets/db_password.txtThe application reads the secret from the file path at
/run/secrets/db_passwordrather than from an environment variable. (Full coverage of Docker Secrets at scale is in the Bonus Topics.) -
Use a secrets manager in production: Tools like AWS Secrets Manager, HashiCorp Vault, or Doppler inject secrets at deploy time rather than storing them in any file at all.
4. Best Practices
-
Keep
.envout of version control: Add.envto.gitignore; commit only.env.examplewith dummy/placeholder values. -
Provide sensible defaults for non-secret config: Use Compose's
${VAR:-default}syntax (as inmulti-container-project/docker-compose.yml) so the project runs out of the box, while still being overridable. -
Validate required variables at startup: Fail fast with a clear error if a required variable is missing, rather than letting the app crash later with a confusing error deep in the code.
-
Never log environment variables: Avoid
console.log(process.env)or similar in production code paths—logs are often less protected than the secrets store itself. -
Scope variables narrowly: Only pass the variables a given service actually needs; don't share one giant
.envacross unrelated services.
5. Different Environments
Most projects need different configuration for local development, staging, and production. Two common approaches:
Multiple .env files, selected explicitly:
# .env.development, .env.staging, .env.production
# Point Compose at a specific file with --env-file
docker compose --env-file .env.production up -d
Compose override files, layered on top of a base docker-compose.yml:
# docker-compose.override.yml (merged automatically in local dev)
services:
api:
environment:
NODE_ENV: development
volumes:
- ./api:/app # bind mount for hot reloading, dev only
# Local dev: docker-compose.yml + docker-compose.override.yml (automatic)
docker compose up
# Production: explicitly select only the production file
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
- Development: favors Bind Mounts, verbose logging, and hot reloading.
- Staging: mirrors production configuration as closely as possible, often with a smaller resource footprint.
- Production: pinned image tags (never
:latest, recap: Part 5), secrets from a secrets manager (not.env), and no bind mounts or dev tooling.
Summary Cheat Sheet
| Task | Command / Syntax |
|---|---|
Load .env into a container | docker run --env-file .env <image> |
| Pass a single variable | docker run -e KEY=value <image> |
| Forward a shell variable | docker run -e KEY <image> |
| Compose: variable substitution | ${VARIABLE} / ${VARIABLE:-default} |
| Compose: select a specific file | docker compose --env-file .env.production up |
| Compose: layer override files | docker compose -f base.yml -f prod.yml up |
| Mount a Docker Secret as a file | secrets: + _FILE env var convention |
ទិញកាហ្វេជូនខ្ញុំ
ចូលចិត្តមេរៀននេះមែនទេ? សូមពិចារណាទិញកាហ្វេជូនខ្ញុំមួយកែវ ដើម្បីគាំទ្រការបង្កើតមាតិកាឥតគិតថ្លៃបន្ថែមទៀត!