Docker Compose transforms multi-container application deployment from a series of manual docker run commands into a declarative, version-controlled configuration. A production-ready Compose deployment includes health checks (to ensure services are truly ready before dependent services start), resource limits (to prevent runaway containers from consuming all host resources), restart policies (to automatically recover from failures), and environment variable management. This guide covers building a production-grade deployment of a full-stack web application — Node.js API, PostgreSQL database, Redis cache, and Nginx reverse proxy — using Docker Compose on RHEL 9.
Prerequisites
- Docker Engine and Docker Compose installed on RHEL 9
Step 1 — Application Dockerfile
# /var/www/myapp/Dockerfile — multi-stage Node.js build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:20-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
USER node
CMD ["node", "server.js"]
Step 2 — Production compose.yaml
# /var/www/myapp/compose.yaml
services:
nginx:
image: nginx:1.27-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
api:
condition: service_healthy
restart: unless-stopped
api:
build: .
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://appuser:${DB_PASSWORD}@db:5432/appdb
- REDIS_URL=redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
cache:
image: redis:7-alpine
command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
restart: unless-stopped
volumes:
postgres_data:
redis_data:
Step 3 — Deploy and Manage
# Set the database password in .env file
echo "DB_PASSWORD=SecurePass123!" > /var/www/myapp/.env
# Deploy the stack
cd /var/www/myapp
docker compose up -d --build
# Verify all services are healthy
docker compose ps
# Run database migrations (one-off command)
docker compose exec api node migrate.js
# Watch logs for errors on first deployment
docker compose logs -f --tail=50
Step 4 — Rolling Update Deployment
# Update and redeploy with zero downtime
git pull origin main
# Rebuild and recreate only changed services
docker compose up -d --build --no-deps api
# Verify the new version is running
docker compose ps
docker compose exec api node --version
Conclusion
Docker Compose on RHEL 9 with health checks, resource limits, and restart policies provides a production-ready single-server deployment that handles service startup ordering, automatic recovery from crashes, and resource isolation. The restart: unless-stopped policy (preferred over always) restarts containers that crash but respects manual docker compose stop commands, preventing unwanted restarts after intentional shutdowns. For high availability across multiple servers, transition to Docker Swarm or Kubernetes.
Next steps: How to Install Docker Engine on RHEL 9, How to Use Docker Volumes on RHEL 9, and How to Install Kubernetes on RHEL 9.