2026-03-31 · DockerSecurityDevOps

Hardcoded Secrets in Docker: How They Get Exposed and How to Find Them

A developer sets up a new service, puts the database password directly in docker-compose.yml to get it working quickly, commits the file to Git, and moves on. Six months later the repo goes public. This is not a hypothetical — it happens constantly and the consequences range from annoying to catastrophic.

Hardcoded secrets are credentials, API keys, passwords, and tokens written directly into configuration files. In the Docker world, they usually show up in docker-compose.yml environment blocks, in .env files that get committed to version control, or in Dockerfile ARG and ENV instructions.

The problem isn't that the value is in the file — it's that the file goes places the secret was never meant to go.

How they leak

Git history. This is the most common leak vector. A developer adds a password to docker-compose.yml, commits it, then replaces it with an environment variable reference in a later commit. The password is gone from the current file but it's permanently preserved in the git history. Anyone who clones the repo — even years later — can see it with git log -p.

# Find secrets in git history:
git log --all -p | grep -i "password|secret|api_key|token" | head -20

Accidentally public repositories. A private repo gets made public. A developer creates a public fork. A CI/CD pipeline logs environment variables. The repo gets added to a job board or portfolio. Any of these exposes the secrets to anyone who looks.

Docker image layers. If you copy a file containing secrets into a Docker image during the build process, that file is baked into the image layer permanently — even if you delete it in a later layer. Anyone who pulls the image can extract the layer and read the file.

# Bad — secret is in the image layer even after deletion:
COPY .env /app/.env
RUN python setup.py
RUN rm /app/.env  # Too late — it's already in layer 2

Container inspection. Any user with Docker socket access can read the environment variables of running containers:

docker inspect container_name | grep -i "env" -A 50

This means anyone with docker group membership on your server can read every secret in every running container.

What hardcoded secrets look like in the wild

# Classic example — everything hardcoded:
services:
  app:
    image: myapp:latest
    environment:
      DATABASE_URL: postgres://admin:SuperSecret123@db:5432/mydb
      API_KEY: sk-abc123def456
      REDIS_PASSWORD: redis_pass_here
      JWT_SECRET: my-super-secret-jwt-key

  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: SuperSecret123
      POSTGRES_USER: admin

This is what the Docker Auditor flags as CRITICAL. Every value in those environment blocks is a literal credential that will be visible to anyone who reads the file.

The correct pattern

# docker-compose.yml — no secrets, only references:
services:
  app:
    image: myapp:latest
    environment:
      DATABASE_URL: ${DATABASE_URL}
      API_KEY: ${API_KEY}
      REDIS_PASSWORD: ${REDIS_PASSWORD}
      JWT_SECRET: ${JWT_SECRET}

  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_USER: ${POSTGRES_USER}
# .env — actual values, never committed to git:
DATABASE_URL=postgres://admin:SuperSecret123@db:5432/mydb
API_KEY=sk-abc123def456
REDIS_PASSWORD=redis_pass_here
JWT_SECRET=my-super-secret-jwt-key
POSTGRES_PASSWORD=SuperSecret123
POSTGRES_USER=admin
# .gitignore — must include .env:
.env
.env.local
.env.production
*.env

The docker-compose.yml is safe to commit. The .env file is not — it contains the actual values and must never leave your server.

Finding hardcoded secrets in your current setup

Check your running docker-compose files:

# Scan for common secret patterns:
grep -rn "password|secret|api_key|token|passwd"   --include="docker-compose*.yml"   --include=".env*" . | grep -v "^\s*#" | grep "=.\{4\}"

Check your git history for previously committed secrets:

# Search across all commits:
git log --all -p -- "*.yml" "*.env" | grep -i "password|secret|key" | grep "^+" | grep -v "^\+\+\+"

If you find secrets in git history — the secret is compromised regardless of whether you've removed it from the current files. Rotate the credential immediately. A removed secret in history is still a leaked secret.

If your secrets are already in a public repo's history: rotate them immediately. Removing the file or the commit doesn't help — GitHub, GitLab, and any forks already have the data. Change the password, revoke the API key, regenerate the JWT secret. Then clean the history with git filter-repo.

Docker Compose secrets (production pattern)

For production setups, Docker has a native secrets mechanism that mounts secrets as files rather than environment variables:

services:
  app:
    image: myapp:latest
    secrets:
      - db_password
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

The secret is mounted at /run/secrets/db_password inside the container. Your application reads it from the file. It never appears in docker inspect output or environment variable listings.

Paste your docker-compose.yml to scan for hardcoded secrets, missing healthchecks, and exposed ports automatically.

Open Docker Auditor →

Related guides