
Docker Compose secrets let you give a service access to a file containing a password, token, or other sensitive value. You declare the source, grant access to the service, and make the application read the mounted file. Those are three separate steps.
This guide uses a harmless dummy value and two Linux-container services to show that flow. The Compose configuration and Python reader were checked locally. We did not pull images or start containers, so the mounted-file checks below are instructions to run in your own test environment.
The top-level secrets section defines the source. A service’s secrets entry grants that service access. With the usual file mount, the application reads a path under /run/secrets/.
| Part | Example | Purpose |
|---|---|---|
| Source file on the host | ./secrets/demo_token.txt | Contains the value supplied to Compose |
| Compose secret name | demo_token | Connects the service grant to the source |
| Target filename | service_token | Places the file at /run/secrets/service_token |
| Application setting | APP_TOKEN_FILE | Tells this example’s reader which path to open |
Declaring a secret at the top level does not grant every service access. Docker’s Compose secrets guide describes the service-level grant and notes that this workflow supports Linux containers. A Windows host running Linux containers is a different case from running Windows containers.
Use a new directory with a secrets subdirectory. Create secrets/demo_token.txt containing the dummy text demo-only-token-not-a-real-secret. No live credential is needed. The two services below use UID and GID 1000, so the sample token and Python script must be readable by that identity through their mounts. Confirm those permissions on your test host.
Save this as compose.yaml:
services:
app:
image: python:3.13-alpine
user: "1000:1000"
network_mode: none
command: ["python", "/app/check_secret.py"]
environment:
APP_TOKEN_FILE: /run/secrets/service_token
volumes:
- ./check_secret.py:/app/check_secret.py:ro
secrets:
- source: demo_token
target: service_token
observer:
image: alpine:3.22
user: "1000:1000"
network_mode: none
command: ["sh", "-c", "test ! -e /run/secrets/service_token && echo 'No secret mounted'"]
secrets:
demo_token:
file: ./secrets/demo_token.txt
The app service receives the secret under the target filename service_token. The observer service has no secret grant. Both have networking disabled because this exercise only checks local file access. The application script is mounted separately as read-only code.
The image tags make the example readable; use tested versions or digests for repeatable deployments. Do not treat the example’s UID or filesystem permissions as suitable for every production image.
Save this as check_secret.py beside the Compose file:
import os
from pathlib import Path
secret_path = os.environ.get("APP_TOKEN_FILE")
if not secret_path:
raise SystemExit("Secret file path is not configured")
try:
token = Path(secret_path).read_bytes()
except OSError:
raise SystemExit("Secret file could not be read")
if not token:
raise SystemExit("Secret file is empty")
print("Secret file loaded; value not logged")
This reader loads bytes, checks that the file is not empty, and prints a status without printing the value. It deliberately does not strip whitespace: a real integration must follow the credential format expected by its provider. A trailing newline may matter.
We ran the reader locally against valid, empty, missing, and unset file paths. The valid case succeeded, the other cases failed with the stated messages, and the dummy value did not appear in output. This checks file handling, not whether a password or token authenticates successfully.
The _FILE suffix is an application convention. Compose does not automatically turn every NAME_FILE variable into a loaded secret. Some images support particular names; this Python script explicitly implements APP_TOKEN_FILE. Consult the image or application documentation before choosing a variable name.
From the example directory, inspect the resolved configuration:
docker compose -p secrets-demo config --quiet
docker compose -p secrets-demo config --format json
docker compose config validates and resolves the model. Check that only app has the secret grant, its target is correct, and APP_TOKEN_FILE contains a path rather than the dummy value. These checks passed with Compose v5.1.4. An intentionally undefined secret reference was also rejected.
Use only the harmless example when sharing rendered output. Docker’s Compose trust model explains that file references and environment resolution can expose host data during configuration loading. A successful parse is not proof that the container user can read a mount.
When a Docker Engine configured for Linux containers is available, run each one-off check:
docker compose -p secrets-demo run --rm app
docker compose -p secrets-demo run --rm observer
The expected messages are Secret file loaded; value not logged for app and No secret mounted for observer. They are expected container results, not results from a container run we performed. The run --rm command removes each one-off container after it exits.
If app fails, examine the configured target, file existence, and permissions under the actual container UID. If observer unexpectedly finds a file, inspect the full service definition for another mount or an image-provided file. The absence check demonstrates the intended mapping; it is not a security audit of the host.
For a file-sourced secret, local Compose uses a bind mount. The service secret reference says that uid, gid, and mode are ignored for this source type. Do not assume adding mode: 0400 to the Compose YAML changes the host file’s permissions.
Protect the source with host ownership, permissions, and access controls that allow the intended application user to read it. Verify the result inside a test container. Avoid granting broad host access merely to solve a read error. Our Docker storage guide explains the distinction between bind mounts and named volumes.
A local file mount does not turn its source into an encrypted vault or prevent a host administrator from reading it. Docker’s bind-mount documentation describes the host dependency. Protect host backups and provisioning paths too, and grant each secret only to services that need it.
For this directory layout, put secrets/ in .gitignore and in .dockerignore if the directory will become a build context. They solve different problems: Git ignore rules affect untracked files, while Docker ignore rules exclude matching files from the build context. Neither removes a credential already committed or included in an image.
Keep ordinary configuration and credential delivery distinct. A .env file is not a secret manager, and storing a token there can still expose it to local tools or copied project files. Our Compose environment-variable guide covers interpolation, env_file, and container environment values.
Compose can also source a secret from a host environment variable through secrets.<name>.environment. That does not mean the value is automatically injected as a container environment variable. It still needs a service grant and application handling. Protect the host and CI environment where that value originates.
A credential needed while building an image belongs in the BuildKit secret workflow. Supply it as a build secret and consume it through a secret mount in the relevant build step. Do not use a Dockerfile ARG or ENV as the credential channel. Also check that the build command does not copy the mounted value into an output artifact.
Docker Swarm secrets use a different deployment mechanism, with encryption in transit and at rest in the swarm. Do not transfer those guarantees to a local file-based Compose example. Likewise, docker secret create is a Swarm command; it is not a prerequisite for the docker compose run workflow shown here.
Changing a host file does not establish that a running application has reread the value. Follow its documented reload procedure or recreate the affected service when required. Docker Compose up supports forced recreation, but that is only the container step; the credential issuer and application must also accept the replacement.
Where the provider supports overlapping credentials, introduce the replacement, verify authentication with it, and then revoke the previous credential. If a value has leaked, follow the provider’s revocation process rather than relying on deletion of a local file.
For troubleshooting, record the service, secret name, target path, container user, and read status. Do not print the token with cat or include it in a diagnostic bundle. Our Docker logs guide covers log inspection and retention. A mounted, readable file is one checkpoint; successful authentication and a verified rotation procedure are separate checks.
Pick one AI, compute, or storage workload and see the difference for yourself. Spin it up in minutes, or let our team map your fastest path to production.