A Docker Compose deployment can work perfectly while quietly exposing database passwords and API keys through environment variables, debug output, process inspection, or a copied .env file. Compose secrets provide a narrower runtime delivery mechanism: a service receives a secret as a file under /run/secrets/ only when that service is explicitly granted access.
This is useful for small VPS deployments and single-host Compose stacks, but it is not the same as a dedicated encrypted secrets manager. Compose file-backed secrets are delivered using bind mounts from the host. Anyone with sufficient host access can still read the source file. This guide shows how to configure them, validate access, and avoid common mistakes.
Symptoms: secrets are leaking into ordinary configuration
Common warning signs include passwords committed in compose.yaml, API keys passed through a shell command, secrets copied into a Dockerfile with ENV or ARG, and sensitive values visible in container environment inspection. Environment variables are convenient, but they can be exposed through process inspection, diagnostic output, crash reports, or application logs. A .env file helps separate configuration from source, but it is not a secrets vault.
Root causes and limits to understand
Compose secrets reduce accidental exposure by mounting a file and granting it only to selected services. They do not automatically encrypt the source file on the host, rotate credentials, or protect a secret from a compromised process that is allowed to read it. Host administrators and anyone with access to the source path remain trusted. Treat the feature as least-privilege delivery, not a complete secrets-management platform.
Another common mistake is assuming that every image supports a _FILE environment variable. Some official images provide this convention, but it is image-specific. Your application may need to read the file directly and must fail safely if the file is missing or unreadable.
Diagnosis: check the current deployment without printing secrets
Inspect the Compose configuration and service list, but avoid commands that dump actual environment values into shared logs:
docker compose config --services
docker compose ps
docker compose config --quiet Review the Compose file in a secure editor. Search for inline passwords, API tokens, private keys, and accidental secret interpolation. Do not paste the output of docker inspect or a fully rendered Compose configuration into a public issue if it may include resolved credentials.
Fix: create a file-backed secret
Create a dedicated directory or use your organization’s approved secret-file location. Keep files outside the application source tree where practical, restrict host permissions, and exclude them from version control. For a local demonstration, create a file interactively so the secret does not appear in shell history:
mkdir -p ./secrets
umask 077
read -rsp "Database password: " DB_PASSWORD
printf '%s' "$DB_PASSWORD" > ./secrets/db_password.txt
unset DB_PASSWORD
chmod 600 ./secrets/db_password.txt Do not use a real production credential in a tutorial or test environment. On a production host, verify the Compose project user, file ownership, backup policy, and filesystem access controls before deployment. The umask and permissions above are examples; the container runtime’s ability to read the bind-mounted file depends on the host and service user configuration.
Declare the secret at the top level and grant it only to the service that needs it. For example:
services:
api:
image: example-api:stable
secrets:
- db_password
environment:
DB_PASSWORD_FILE: /run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password.txt Inside the container, the secret is mounted at /run/secrets/db_password. The example sets DB_PASSWORD_FILE as a pointer to the file, not as the password itself. Your application must explicitly read that path. If the image supports a documented DB_PASSWORD_FILE convention, use it; otherwise, update the application configuration to read the file safely.
For a database image that supports a documented file-based password variable, follow that image’s official documentation. Do not assume that an arbitrary environment variable ending in _FILE has special behavior.
Build-time secrets are a separate concern
Runtime Compose secrets and build secrets solve different problems. If a private package token is needed while building an image, do not copy it into the build context or write it into a Dockerfile layer. Use BuildKit build secrets with a supported Dockerfile frontend and a build step that consumes the mounted secret without persisting it. Remove the secret from the build process once the dependency download is complete. A runtime secret does not automatically protect a credential used during image construction.
Validation: verify access without revealing the value
Validate the Compose configuration and start only the affected service or project according to your release procedure:
docker compose config --quiet
docker compose up -d api
docker compose ps Then verify that the expected file exists and is readable inside the service without printing its contents:
docker compose exec api sh -c 'test -r /run/secrets/db_password'
docker compose exec api sh -c 'test -s /run/secrets/db_password' If the application image has no shell, use its health endpoint or a built-in diagnostic command instead of installing a shell in production. Confirm that the application can authenticate to the database and that a service without an explicit secret grant cannot access the secret. Do not use cat /run/secrets/db_password as a test in shared terminals or CI logs.
Common failure modes
- File not found: check the source path relative to the Compose file and ensure the file exists on the host.
- Permission denied: verify host ownership, mount behavior, container UID/GID, and the service’s filesystem restrictions. Do not fix it with world-readable permissions.
- Application ignores the file: confirm that the application actually reads the configured file path;
_FILEis not a universal Compose feature. - Secret appears in logs: remove value logging and rotate the credential if it was exposed.
- Works locally, fails in CI: ensure the secret file is provisioned securely on the runner and not assumed to exist in the checked-out repository.
Rollback and rotation
If the new configuration breaks startup, revert the Compose change and restore the previous approved configuration without reintroducing credentials into source control. If a secret may have leaked, revoke or rotate it at the issuing service first, then update the host-side secret file and redeploy dependent services. A changed file does not guarantee that an already-running process reloads the credential; follow the application’s documented reload or restart behavior.
Keep a controlled rollback path for the application release, but do not restore a compromised password simply to make rollback easier. Confirm that the new credential works and that the old one is revoked when the rotation is complete.
Production checklist
- Keep secret files out of Git and Docker build contexts.
- Restrict host file permissions and access to the Docker host.
- Grant each secret only to the services that require it.
- Use file-based application configuration and image-supported
_FILEoptions only where documented. - Validate readability without printing secret values.
- Use BuildKit secrets for build-time credentials.
- Rotate credentials after exposure and verify the old credential is revoked.
- Use a dedicated secrets manager when centralized access, audit, rotation, or multi-host distribution is required.
Primary references
Docker: Manage secrets securely in Docker Compose; Docker Compose secrets reference; Docker Build secrets; Docker Compose environment-variable best practices. Related ProdCraft guides: Docker Compose with FastAPI, PostgreSQL and Redis and Secure webhook handling for AI agents.




