Docker Compose simplifies running multi-container applications by defining every service, network, and volume in a single docker-compose.yml file. Instead of typing long docker run commands for each container, you declare the desired state and let Compose handle the orchestration. On RHEL 8 with Docker CE installed, Docker Compose V2 ships as a plugin and is invoked with docker compose (no hyphen). This tutorial builds a realistic three-tier stack — a web application, a PostgreSQL database, and a Redis cache — and covers environment variables, health checks, scaling, and production considerations.
Prerequisites
- RHEL 8 with Docker CE and the Docker Compose plugin installed
- Root or
sudoaccess - A working directory for your project files
- Basic familiarity with YAML syntax
Step 1 — Create the Project Directory and .env File
Separate configuration from the Compose file using a .env file. Docker Compose automatically loads it when you run any docker compose command in the same directory.
mkdir -p ~/myapp && cd ~/myapp
cat > .env <<'EOF'
POSTGRES_DB=appdb
POSTGRES_USER=appuser
POSTGRES_PASSWORD=SecurePass!42
REDIS_PASSWORD=RedisPass!99
APP_PORT=8080
EOF
Step 2 — Write the docker-compose.yml
Create a docker-compose.yml file that ties together the application, database, and cache. The services section defines each container, networks isolates traffic, and volumes persists data.
cat > docker-compose.yml <<'EOF'
version: "3.9"
services:
app:
build: .
image: myapp:latest
ports:
- "${APP_PORT}:3000"
environment:
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
- REDIS_URL=redis://:${REDIS_PASSWORD}@cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
networks:
- frontend
- backend
restart: unless-stopped
db:
image: postgres:15-alpine
volumes:
- pg_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
networks:
- backend
restart: unless-stopped
cache:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- backend
restart: unless-stopped
networks:
frontend:
backend:
internal: true
volumes:
pg_data:
redis_data:
EOF
Step 3 — Build and Start the Stack
Build any images defined with a build key and start all services in detached mode. The --build flag forces a rebuild even if the image already exists.
sudo docker compose up -d --build
Check that every service started and passed its health check:
sudo docker compose ps
sudo docker compose logs --tail=30
Step 4 — Interact with Running Services
Use docker compose exec to run commands inside a running container. This is useful for running database migrations, opening a shell, or querying Redis.
# Open a PostgreSQL prompt
sudo docker compose exec db psql -U appuser -d appdb
# Open a Redis CLI session
sudo docker compose exec cache redis-cli -a RedisPass!99
# Open a shell in the app container
sudo docker compose exec app sh
Step 5 — Scale the Application Service
Compose can run multiple replicas of a stateless service. Remove the fixed host port from the app service first (use a load balancer instead), then scale horizontally:
sudo docker compose up -d --scale app=3 --no-recreate
Verify the three replicas are running:
sudo docker compose ps app
Step 6 — Production Considerations
For production deployments, store secrets in a secrets manager rather than a plain .env file, enable Docker log rotation, and pin image tags to prevent unplanned updates:
# Add log rotation to /etc/docker/daemon.json
sudo tee /etc/docker/daemon.json <<'EOF'
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "5"
}
}
EOF
sudo systemctl restart docker
# Tear down and remove volumes (destructive — backup first)
sudo docker compose down -v
Conclusion
You have deployed a multi-container application stack on RHEL 8 using Docker Compose, complete with a PostgreSQL database, Redis cache, health checks, isolated networks, and persistent volumes. The .env file keeps credentials out of the Compose definition, making the same file reusable across environments by swapping variable values. Scaling stateless services is a single command, and docker compose exec gives immediate access to any running container for debugging and maintenance.
Next steps: How to Install Portainer for Docker Management on RHEL 8, How to Set Up a Private Docker Registry on RHEL 8, and How to Install Podman as a Rootless Docker Alternative on RHEL 8.