Compose, Environment Files, and Secrets
Compose should define services, pinned images, private networks, volumes, health checks, and non-secret defaults. Real secrets belong outside Git and should be injected at runtime through the strongest mechanism the platform supports.
What You Will Be Able to Decide
- Explain the role of compose, environment files, and secrets in a self-hosted system.
- Apply the procedure to a real open-source deployment.
- Recognise unsafe defaults and verify the resulting control.
- Record enough evidence for another operator to repeat or recover the work.
Most useful self-hosted applications are systems rather than single processes. An application, database, queue, and reverse proxy may have different lifecycles but still need one understandable deployment definition.
Docker Compose expresses those relationships in YAML. It can create private networks and named volumes, pass configuration, order startup dependencies, and define health checks.
An environment file is convenient, but convenience is not encryption. Its filesystem permissions, backup path, platform visibility, and Git exclusions still matter.
Technical term
Runtime secret
Sensitive data made available to a running service without baking it into the image or committing it to source control.
It is a key issued when a worker starts a shift, not a key photographed into the construction plans.
The Working Model
Compose interpolation can read values from a local `.env`, while a service-level `env_file` passes variables into the container. Those are different stages. Use `docker compose config` to inspect the resolved model, but avoid exposing its output when it includes secret values.
Put databases and supporting services on a private network. Use named volumes for durable data and document exactly what each volume contains. A volume is persistence, not a backup: deletion, corruption, or host loss can still remove it.
Where the deployment platform supports secret files or an external secret manager, prefer them for high-value credentials. Always restrict file permissions and rotate any credential that has entered Git history, logs, screenshots, or shell history.
Implementation Procedure
- Copy `.env.example` to `.env`, generate unique values, restrict access, and confirm `.env` is ignored by Git.
- Pin each service image and define a restart policy.
- Create a private application network and publish only the web entry point.
- Declare named volumes for databases, uploads, and other durable paths.
- Resolve and review the Compose model, start it, inspect health, then recreate it to test persistence.
services:
app:
image: ghcr.io/example/project:1.4.2
restart: unless-stopped
env_file: .env
ports:
- "127.0.0.1:8080:3000"
volumes:
- app-data:/data
networks: [private]
volumes:
app-data:
networks:
private:
internal: trueControlled Practice and Fragile Practice
Verification and Recovery Evidence
- `docker compose ps` reports the intended services and health states.
- The database cannot be reached directly from the public internet.
- A fresh container can use the existing volume and a restore test can rebuild that volume from backup.
Warning Signs
- The Compose file uses default credentials shown in upstream examples.
- No one knows which volume contains uploads or the primary database.
- Resolved configuration is pasted into public support channels.
Questions to Ask a Consultant
- Which configuration is safe to commit and which values require rotation?
- Can every named volume be mapped to a backup and restore procedure?
- Which services genuinely need to communicate with each other?
Key takeaway
Key Takeaway
Compose turns architecture into a reviewable file. Use that visibility to constrain networks, declare persistence, and keep real secrets out of source history.