Skip to main content
AllDevToolsHub
2026-05-07
Last reviewed: Aug 2026
DEVOPS
Est Read: 07_MIN

Docker Compose Secrets: Stop Putting Credentials in docker-compose.yml

Docker Compose Secrets: Stop Putting Credentials in docker-compose.yml
Processing_Node: 01

#1Docker Compose secrets: keep credentials out of docker-compose.yml

What we tested: We validated environment files, Docker configurations, and CI pipeline syntax using the browser-based tools on this site. All parsing and validation runs locally.

I still see teams commit credentials into docker-compose.yml because it is the quickest way to get a stack running.

The problem is that convenience becomes habit, and that habit leaves secrets in Git history. The safer pattern is to keep the compose file for service wiring and move secrets into local env files or a real secret manager.


#2The problem: what not to do

yaml
# ❌ This is in thousands of public GitHub repos right now
version: '3.8'
services:
  api:
    image: myapp:latest
    environment:
      DATABASE_URL: "postgresql://admin:mysuperpassword@db:5432/production"
      STRIPE_SECRET_KEY: "sk_live_abc123realkey"
      JWT_SECRET: "my-super-secret-jwt-key-that-is-not-random"
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: "mysuperpassword"

This file will be committed to Git. It will be in your Git history forever. If the repo is ever made public, or if a laptop gets compromised, every credential in that file is exposed.

I have seen this happen in real teams: someone “just” adds a test password to get the stack running, then the file gets copied to staging and never cleaned up. The fix is to make the safe path easier than the unsafe one.

#2The pattern I recommend

Use the compose file for service wiring, not secret storage. Put only non-sensitive defaults in the repo, and keep credentials in files that are ignored locally or injected by the deployment platform.


#2Level 1. The .env File Pattern

The correct baseline for local development:

yaml
# docker-compose.yml, NO credentials here
version: '3.8'
services:
  api:
    image: myapp:latest
    env_file:
      - .env
    # OR reference variables from shell environment
    environment:
      - DATABASE_URL       # reads DATABASE_URL from current shell or .env
      - REDIS_URL
      - SECRET_KEY
      - STRIPE_SECRET_KEY
  
  db:
    image: postgres:16
    env_file:
      - .env.db
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
bash
# .env, gitignored, local only
DATABASE_URL=postgresql://admin:localpassword@db:5432/myapp_dev
REDIS_URL=redis://redis:6379
SECRET_KEY=dev-only-secret-not-real-please-change
STRIPE_SECRET_KEY=sk_test_abc123_test_key_not_real
POSTGRES_DB=myapp_dev
POSTGRES_USER=admin
POSTGRES_PASSWORD=localpassword
bash
# .gitignore, critical
.env
.env.*
!.env.example
bash
# .env.example, COMMITTED, documents required variables
DATABASE_URL=postgresql://user:password@db:5432/dbname
REDIS_URL=redis://redis:6379
SECRET_KEY=                     # generate: openssl rand -hex 32
STRIPE_SECRET_KEY=              # from stripe.com dashboard, test mode
POSTGRES_DB=myapp_dev
POSTGRES_USER=admin
POSTGRES_PASSWORD=              # choose a strong local password

#2Level 2, Multiple Environment Files

For teams with separate configs per environment:

yaml
# docker-compose.yml, base config, committed
services:
  api:
    image: myapp:latest
    environment:
      - NODE_ENV
      - PORT=3000
yaml
# docker-compose.override.yml, local dev overrides, gitignored
services:
  api:
    env_file:
      - .env.local
    ports:
      - "3000:3000"   # only expose locally
    volumes:
      - ./src:/app/src  # hot reload in dev
yaml
# docker-compose.prod.yml, production, no local mounts
services:
  api:
    env_file:
      - /etc/myapp/prod.env  # read from server filesystem, not repo
    restart: unless-stopped
bash
# Local dev, uses docker-compose.yml + docker-compose.override.yml automatically
docker compose up

# Production, explicitly specify files
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

#2Level 3, Docker Secrets (Swarm Mode)

Docker Secrets provide encrypted-at-rest, memory-only access to sensitive data. Available in Docker Swarm mode (not plain docker compose):

yaml
# docker-compose.yml with Docker Secrets
version: '3.8'
services:
  api:
    image: myapp:latest
    environment:
      # Non-sensitive config as env vars
      NODE_ENV: production
      PORT: 3000
      DATABASE_HOST: db
      DATABASE_NAME: myapp
    secrets:
      - db_password
      - stripe_key
      - jwt_secret

secrets:
  db_password:
    external: true   # managed by Docker Swarm secret store
  stripe_key:
    external: true
  jwt_secret:
    external: true
bash
# Create secrets (run on the Swarm manager node)
echo "supersecretpassword" | docker secret create db_password -
echo "sk_live_realkey" | docker secret create stripe_key -
openssl rand -hex 32 | docker secret create jwt_secret -

Secrets are mounted as files at /run/secrets/<secret_name> inside the container:

javascript
// Read secret from file in your app
const fs = require('fs');

function getSecret(name) {
  try {
    return fs.readFileSync(`/run/secrets/${name}`, 'utf8').trim();
  } catch {
    // Fallback to environment variable for local dev
    return process.env[name.toUpperCase()];
  }
}

const dbPassword = getSecret('db_password');

#2Level 4, External Secret Managers

For production Kubernetes or cloud deployments, external secret managers are the right answer:

#3Doppler

yaml
# docker-compose.yml with Doppler CLI
services:
  api:
    image: myapp:latest
    # No env_file needed, Doppler injects at runtime
bash
# Run with secrets injected from Doppler
doppler run -- docker compose up

# Or use the Doppler Docker image to inject into any container
docker run --rm \
  -e DOPPLER_TOKEN="dp.st.your.service.token" \
  dopplerhq/cli \
  run -- myapp:latest

#3AWS Secrets Manager (with ECS)

json
// ECS Task Definition, secrets pulled from AWS Secrets Manager
{
  "containerDefinitions": [{
    "name": "api",
    "image": "myapp:latest",
    "environment": [
      { "name": "NODE_ENV", "value": "production" }
    ],
    "secrets": [
      {
        "name": "DATABASE_URL",
        "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789:secret:myapp/database-url"
      },
      {
        "name": "STRIPE_SECRET_KEY",
        "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789:secret:myapp/stripe-key"
      }
    ]
  }]
}

#3GitHub Actions CI/CD

yaml
# .github/workflows/deploy.yml
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to production
        env:
          DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
          STRIPE_SECRET_KEY: ${{ secrets.PROD_STRIPE_KEY }}
        run: |
          docker compose -f docker-compose.prod.yml up -d

#2Debugging, Inspect What Variables a Container Sees

bash
# Show all environment variables in a running container
docker exec <container_id> env

# Show specific variable
docker exec <container_id> printenv DATABASE_URL

# Show what compose thinks the variables will be (before running)
docker compose config

#2Common Mistakes

  • Committing .env instead of .env.example. The most common mistake. Always check git status before committing. Add a pre-commit hook to block .env commits.
  • Using the same credentials for dev and production. If a developer's laptop is stolen, production should not be compromised. Every environment needs its own set of credentials.
  • Putting secrets in image labels or ARG instructions. Docker build arguments are stored in the image's history. docker image history myapp reveals them. Use multi-stage builds and pass secrets at runtime, not build time.
  • Logging the entire environment with console.log(process.env) in debug mode. This dumps all secrets into your log aggregator. Log specific, non-sensitive config values only.
  • Checking docker-compose.yml into .env's path. Some tools auto-read files, make sure your .env file is not in a path where another tool might pick it up and log it.

#2Try It In Your Browser

To validate and inspect environment variable syntax before wiring up Docker Compose, paste your .env file content into the AllDevToolsHub Environment Variable Parser. It shows all variables, their values, and flags common formatting issues, all locally in your browser.


#2Frequently Asked Questions

#3Can I use Docker Secrets in docker compose without Swarm mode?

Partially. Compose supports a file: secret source that mounts a local file, this works without Swarm. But external: true secrets require Swarm. For non-Swarm local dev, use file: or the env_file approach instead.

#3How do I rotate a secret without downtime?

In Docker Swarm: create a new secret (docker secret create db_password_v2 -), update the service to reference the new secret, redeploy, then remove the old secret. Your app needs to support reading a secret name with a version suffix, or you use a secret manager (like Doppler or Vault) that handles rotation transparently.

#3Should I use env_file or environment in docker-compose.yml?

Use env_file when you have many variables, cleaner. Use environment when you want to explicitly list which variables the container receives, and whether values come from the host or are hardcoded (non-sensitive) defaults. Many teams use both: env_file for secrets, environment for non-sensitive config like NODE_ENV: production.

#3Is it safe to pass environment variables on the docker run command line?

The values are visible to other processes on the same host via ps aux. For production servers, this is acceptable (the machine should be controlled). For CI systems with shared runners, use secrets storage instead. Never pass secrets as docker run arguments in scripts committed to Git.

#3What is the difference between Docker Secrets and Kubernetes Secrets?

Both mount secrets as files inside containers. Docker Secrets (Swarm) are encrypted at rest and in transit between manager and worker nodes. Kubernetes Secrets are base64-encoded but stored as plaintext in etcd by default, you need additional configuration (Encryption at Rest) or an External Secrets Operator to get equivalent security.

#2Try These Tools

  • .env File Parser — Audit and validate your .env files for syntax errors, duplicate keys, and missing values.
  • JSON Formatter — Format and validate JSON config payloads before passing them to containers.

Written by Rahul Jalavadiya, founder of AllDevToolsHub. All tools run locally in your browser.

#2Sources / Further reading

Quick Summary

>- How to manage secrets securely in Docker Compose in 2026. Covers the difference between environment variables, .env files, Docker Secrets, and external secret managers. Includes real-world patterns for local development and production, with examples for Node.js, Python, and Go.

Key Takeaways

Key Takeaways

  • Docker Compose secrets can be passed via environment variables, files, or Docker secrets — each has different security trade-offs.
  • Never bake secrets into Docker images — they persist in layer history even after deletion.
  • Use Docker secrets (swarm mode) or external vaults for production; .env files are acceptable only for local development.
Use Cases

When to use it

  • Passing database credentials to a local development stack without committing them to git.
  • Configuring API keys for a staging environment via Docker Compose profiles.
  • Managing per-developer .env overrides in a team project.
Watch out

Common Mistakes

  • Committing .env files to git — credentials remain in history even after force-push.
  • Using the same .env file across dev, staging, and production — one leaked file compromises all environments.
  • Forgetting that docker compose config renders secrets in plain text when debugging.
FAQ

Docker Compose Secrets: Stop Putting Credentials in docker-compose.yml, Frequently Asked

What is the safest way to pass secrets to Docker Compose?

For production, use Docker secrets (swarm mode) or an external vault like HashiCorp Vault. For local development, use .env files listed in .gitignore with a .env.example template committed to the repo.

Can Docker secrets be rotated without rebuilding the image?

Yes. Docker secrets are mounted at runtime and can be rotated by updating the secret in the swarm and restarting services. The image itself never changes.

RJRahul JalavadiyaFounder & Lead Engineer
Published 2026-05-07Last reviewed 2026-08-23

Tools Mentioned in This Article

Tools, tactics, and toughened-up tips, once a week

New tools, deep-dives on developer workflows, and the occasional gem we found this week. No spam, no tracking. Unsubscribe anytime.

Found an error or have feedback?

We correct errors quickly and document changes in our changelog. Report issues at support@alldevtoolshub.com.

Last reviewed: 2026-08-23
Security Memo
AT

Rahul Jalavadiya

Engineering Protocol V1

Specializing in local-first architecture and Zero-Trust developer workflows. No data leaves the machine.