Compose supports several configuration paths, but they have different security properties. A .env file is useful for interpolation; it is not a secret vault. This lab separates ordinary application settings from a database password mounted as a read-only file.
1. Create project settings
mkdir -p ~/compose-config-demo/secrets && cd ~/compose-config-demo
cat > .env <<'EOF'
COMPOSE_PROJECT_NAME=config-demo
APP_PORT=127.0.0.1:8080
POSTGRES_IMAGE=postgres:17-alpine
EOF
printf 'replace-with-a-long-random-password' > secrets/db_password.txt
chmod 600 secrets/db_password.txt
Generate a real random value with a password manager or openssl rand -base64 32; do not reuse the tutorial value.
2. Define the Compose stack
services:
db:
image: ${POSTGRES_IMAGE}
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 5s
retries: 10
adminer:
image: adminer:5
ports:
- "${APP_PORT}:8080"
depends_on:
db:
condition: service_healthy
secrets:
db_password:
file: ./secrets/db_password.txt
volumes:
db-data:
3. Prevent accidental commits
cat > .gitignore <<'EOF'
.env
.env.*
!.env.example
secrets/
EOF
cat > .env.example <<'EOF'
COMPOSE_PROJECT_NAME=config-demo
APP_PORT=127.0.0.1:8080
POSTGRES_IMAGE=postgres:17-alpine
EOF
4. Inspect interpolation without printing secrets
docker compose config --environment
docker compose config --services
docker compose config --images
docker compose config --quiet
Be careful with plain docker compose config in CI logs because interpolated environment values may appear. File-based secrets are mounted at /run/secrets/NAME and are not intended to become environment variables.
5. Start and verify secret mounting
docker compose up -d
docker compose ps
docker compose exec db sh -c 'test -r /run/secrets/db_password && stat -c "%a %n" /run/secrets/db_password'
docker compose exec db psql -U app -d app -c 'select current_database(), current_user;'
6. Override non-secret settings
APP_PORT=127.0.0.1:9090 docker compose config --quiet
docker compose --env-file .env up -d
Compose interpolation precedence can surprise operators. Render and review the resolved model before deployment.
7. Rotate the database password
Changing the file alone does not change an existing PostgreSQL role. Update the database credential first, replace the file securely, then recreate dependent services during a controlled window.
NEW_PASSWORD=$(openssl rand -base64 32)
docker compose exec -T db psql -U app -d app
-c "ALTER USER app WITH PASSWORD '$NEW_PASSWORD';"
printf '%s' "$NEW_PASSWORD" > secrets/db_password.txt
unset NEW_PASSWORD
docker compose up -d --force-recreate
8. Clean up
docker compose down
# Add --volumes only when database data may be destroyed
Rules to keep
- Do not commit real
.envor secret files. - Prefer mounted files or an external secret manager for credentials.
- Never bake secrets into Dockerfiles, image layers, or build arguments.
- Limit filesystem permissions and CI log output.
- Rotate credentials and test application reconnection.
References: Docker documentation and the Server World topic index.