
Docker Compose for Production: Secure and Scalable Deployment Configuration
Docker Compose is famous for simplifying local development environments. However, with the right configurations, Docker Compose is also a highly effective, lightweight orchestration tool for single-host production deployments.
Deploying to production requires a shift in how you configure containers. You must prioritize security, resource constraints, persistent storage, and automatic recovery from system crashes.
In this guide, we will explore production-grade Docker Compose patterns, cover security hardening techniques, and write a secure, resource-constrained docker-compose.yml template.
Production Best Practices Checklist
When moving from local development to production, check your Compose configuration against these rules:
1. Lock Specific Image Tags
Never use the latest tag in production. The latest tag points to whatever build was pushed most recently, which can introduce breaking changes during automated deployments. Always pin your images to specific semantic version tags.
# Avoid
image: node:latest
# Use
image: node:20.11.0-alpine2. Impose Resource Constraints
By default, a container can consume all available host CPU and memory, potentially starving other essential services (like your database or web proxy). Always define explicit resource limits.
deploy:
resources:
limits:
cpus: '0.50'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M3. Configure Restart Policies
Containers can crash due to code exceptions or host system restarts. Ensure your services recover automatically using the unless-stopped restart policy.
restart: unless-stoppedThis ensures the container starts automatically when the host server reboots, unless you manually executed docker compose stop.
4. Run as Non-Root Users
By default, Docker containers execute processes as the host root user. If your application has a remote code execution vulnerability, attackers can gain root access to the entire host server.
Always write your Dockerfiles to switch to a non-root user (e.g., node, alpine, or custom uid), and ensure the container runtime runs in read-only mode where possible:
read_only: true
security_opt:
- no-new-privileges:trueA Production-Ready Docker Compose Template
Here is a secure, production-grade Docker Compose configuration featuring a Node.js web application and a PostgreSQL database.
Create a file called docker-compose.prod.yml:
version: '3.8'
services:
web:
image: myapp/node-app:1.4.2-alpine
container_name: prod-web-app
restart: unless-stopped
read_only: true
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp
environment:
NODE_ENV: production
DATABASE_URL: postgresql://db_user:${DB_PASSWORD}@db:5432/prod_db
ports:
- "127.0.0.1:3000:3000"
depends_on:
db:
condition: service_healthy
deploy:
resources:
limits:
cpus: '1.0'
memory: 1024M
reservations:
cpus: '0.5'
memory: 512M
db:
image: postgres:16.1-alpine
container_name: prod-database
restart: unless-stopped
environment:
POSTGRES_USER: db_user
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: prod_db
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "127.0.0.1:5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U db_user -d prod_db"]
interval: 10s
timeout: 5s
retries: 5
deploy:
resources:
limits:
cpus: '2.0'
memory: 2048M
volumes:
pgdata:
driver: localCrucial Configurations Explained
- Ports Binding: Instead of mapping ports to
"3000:3000", the template binds to"127.0.0.1:3000:3000". This restricts container ports to the local loopback interface, meaning external traffic cannot access the containers directly. Access is routed securely through a reverse proxy (like Nginx) running on the host. - Service Healthchecks: The web application relies on the database being fully ready to accept connections. We define a native
healthcheckon the database service and configure the web app'sdepends_onblock withcondition: service_healthyto guarantee correct startup order. - Environment Secrets: Secrets like
${DB_PASSWORD}are not hardcoded. Docker Compose automatically resolves these variables from a local.envfile on the host machine during startup.
Conclusion
Docker Compose provides a robust, low-complexity deployment pipeline for single-instance production setups. By locking image tags, restricting CPU and memory utilization, locking down execution permissions to non-root accounts, and binding ports locally, you can deploy applications securely without needing the operational complexity of Kubernetes.