docker.recipes
Security7 min read

Docker Secrets Without Swarm Mode

Securely manage sensitive data in Docker Compose using the _FILE suffix pattern and compose secrets.

01Why Environment Variables Are Risky

Environment variables are the default way to pass configuration to containers, but they have security issues: **Problems:** • Visible in docker inspect output • Logged in many debugging scenarios • Inherited by child processes • Often accidentally committed to version control • Visible in process listings on some systems **Better approach:** Mount secrets as files. Many applications support reading sensitive values from files using the _FILE suffix convention.

Anyone with access to docker inspect or the Docker socket can read all environment variables from running containers.

02The _FILE Suffix Pattern

Many official images support a _FILE suffix variant of their environment variables. Instead of passing the secret directly, you pass a path to a file containing the secret.
1services:
2 # Traditional (insecure) approach
3 database-insecure:
4 image: postgres:16
5 environment:
6 - POSTGRES_PASSWORD=supersecretpassword # Visible everywhere!
7
8 # Secure approach using _FILE suffix
9 database-secure:
10 image: postgres:16
11 environment:
12 - POSTGRES_PASSWORD_FILE=/run/secrets/db_password
13 volumes:
14 - ./secrets/db_password:/run/secrets/db_password:ro
15
16# Create the secret file
17# echo "supersecretpassword" > ./secrets/db_password
18# chmod 600 ./secrets/db_password

Check the image documentation for _FILE suffix support. Most official database images support it.

03Using Docker Compose Secrets

Docker Compose has built-in secrets support that works without Swarm mode. Secrets are mounted as files in /run/secrets/.
1services:
2 database:
3 image: postgres:16
4 environment:
5 - POSTGRES_PASSWORD_FILE=/run/secrets/db_password
6 secrets:
7 - db_password
8
9 app:
10 image: myapp:latest
11 environment:
12 - DATABASE_PASSWORD_FILE=/run/secrets/db_password
13 - API_KEY_FILE=/run/secrets/api_key
14 secrets:
15 - db_password
16 - api_key
17
18secrets:
19 db_password:
20 file: ./secrets/db_password.txt
21 api_key:
22 file: ./secrets/api_key.txt
23
24# Secrets are mounted at /run/secrets/<secret_name>
25# They're read-only with restricted permissions

04Creating and Managing Secret Files

Set up your secrets directory with proper permissions and git-ignore the files.
1# Create secrets directory
2mkdir -p secrets
3chmod 700 secrets
4
5# Create secret files
6echo "your-db-password" > secrets/db_password.txt
7echo "your-api-key" > secrets/api_key.txt
8
9# Set restrictive permissions
10chmod 600 secrets/*
11
12# Add to .gitignore
13echo "secrets/" >> .gitignore
14
15# Verify files aren't tracked
16git status
17
18# For production, consider using:
19# - Password managers with CLI (1Password, Bitwarden)
20# - Cloud secret managers (AWS Secrets Manager, etc.)
21# - Ansible vault, SOPS, etc.

Never commit secret files to version control. Use .gitignore and verify with git status before committing.

05Reading Secrets in Your Application

If your image doesn't support _FILE suffix, you can read secrets in your application code or entrypoint script.
1# Custom entrypoint script (entrypoint.sh)
2#!/bin/sh
3
4# Read secrets from files into environment variables
5if [ -f /run/secrets/db_password ]; then
6 export DATABASE_PASSWORD=$(cat /run/secrets/db_password)
7fi
8
9if [ -f /run/secrets/api_key ]; then
10 export API_KEY=$(cat /run/secrets/api_key)
11fi
12
13# Run the actual command
14exec "$@"

06Using Secrets in Compose File

Here's a complete example using the entrypoint approach.
1# docker-compose.yml
2services:
3 app:
4 build: .
5 entrypoint: ["/app/entrypoint.sh"]
6 command: ["python", "app.py"]
7 secrets:
8 - db_password
9 - api_key
10
11secrets:
12 db_password:
13 file: ./secrets/db_password.txt
14 api_key:
15 file: ./secrets/api_key.txt
16
17---
18# Dockerfile
19FROM python: 3.12-slim
20WORKDIR /app
21COPY requirements.txt .
22RUN pip install -r requirements.txt
23COPY . .
24RUN chmod +x entrypoint.sh
25# Don't set entrypoint here - let compose override it

The entrypoint pattern works with any image, even if it doesn't support _FILE suffix natively.

07Using .env Files (Less Secure Alternative)

The .env file approach is better than hardcoding but less secure than proper secrets. Use it for development only.
1# docker-compose.yml
2services:
3 database:
4 image: postgres:16
5 environment:
6 - POSTGRES_PASSWORD=${DB_PASSWORD}
7
8 app:
9 image: myapp:latest
10 environment:
11 - DATABASE_URL=postgres://user:${DB_PASSWORD}@database:5432/mydb
12 - API_KEY=${API_KEY}
13
14# .env file (git-ignored!)
15# DB_PASSWORD=your-password-here
16# API_KEY=your-api-key-here

.env files are still environment variables under the hood. They're visible in docker inspect and can leak. Use proper secrets for production.