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 →