How to Deploy Applications with Docker Compose on RHEL 7

Docker Compose lets you define and run multi-container applications using a single YAML file. Instead of managing individual docker run commands for each service, you describe the entire application stack — web server, database, cache, message queue — in a docker-compose.yml file and bring everything up with one command. This tutorial covers installing Docker Compose on RHEL 7, writing a realistic multi-service compose file, managing environment variables and secrets with .env files, using named volumes and custom networks, and operating the stack day-to-day with common Compose commands.

Prerequisites

  • RHEL 7 with Docker CE installed and running
  • Root or sudo access
  • Internet access (or a local mirror) to pull Docker images
  • At least 1 GB free RAM beyond what the OS needs

Step 1: Install Docker Compose

Docker Compose is not packaged in the RHEL 7 base or extras repos, so install the binary directly from GitHub:

COMPOSE_VERSION=$(curl -s 
  https://api.github.com/repos/docker/compose/releases/latest 
  | grep '"tag_name"' | cut -d'"' -f4)

sudo curl -L 
  "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" 
  -o /usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose

# Verify
docker-compose --version

If the server has no internet access, copy the binary from another machine or install via pip:

sudo yum install -y python3-pip
sudo pip3 install docker-compose

Step 2: Create the Project Directory

Compose treats each directory as a project. Create a structured directory for the application:

mkdir -p ~/myapp/{app,nginx,data/mysql,data/redis}
cd ~/myapp

Step 3: Create the .env File for Variables

Storing configuration values in a .env file keeps them out of the compose file and makes environment-specific overrides easy. Compose automatically reads .env from the same directory:

cat > .env <<'EOF'
# Application settings
APP_PORT=8080
APP_ENV=production

# MySQL
MYSQL_ROOT_PASSWORD=R00tSecurePass!
MYSQL_DATABASE=appdb
MYSQL_USER=appuser
MYSQL_PASSWORD=AppPass2024!

# Redis
REDIS_PASSWORD=RedisPass2024!

# Domain
DOMAIN=myapp.example.com
EOF

Set restrictive permissions so other users cannot read the credentials:

chmod 600 .env

Step 4: Write the docker-compose.yml

The following compose file defines four services: an Nginx web server, a Python/Node web application, a MySQL database, and a Redis cache. Services communicate over a dedicated internal network:

cat > docker-compose.yml <<'EOF'
version: "3.8"

services:

  # --- Nginx reverse proxy / web server ---
  nginx:
    image: nginx:1.25-alpine
    container_name: myapp_nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/certs:/etc/nginx/certs:ro
      - static_files:/var/www/static:ro
    networks:
      - frontend
    depends_on:
      - app

  # --- Application service ---
  app:
    image: myapp:latest
    container_name: myapp_app
    restart: unless-stopped
    env_file:
      - .env
    environment:
      - DB_HOST=db
      - DB_PORT=3306
      - DB_NAME=${MYSQL_DATABASE}
      - DB_USER=${MYSQL_USER}
      - DB_PASS=${MYSQL_PASSWORD}
      - CACHE_HOST=cache
      - CACHE_PORT=6379
      - CACHE_PASS=${REDIS_PASSWORD}
      - APP_ENV=${APP_ENV}
    volumes:
      - ./app:/app
      - static_files:/app/static
    networks:
      - frontend
      - backend
    depends_on:
      - db
      - cache

  # --- MySQL database ---
  db:
    image: mysql:8.0
    container_name: myapp_db
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=${MYSQL_DATABASE}
      - MYSQL_USER=${MYSQL_USER}
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
    volumes:
      - ./data/mysql:/var/lib/mysql
    networks:
      - backend
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost",
             "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5

  # --- Redis cache ---
  cache:
    image: redis:7-alpine
    container_name: myapp_cache
    restart: unless-stopped
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - ./data/redis:/data
    networks:
      - backend

volumes:
  static_files:

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true
EOF

Key points in this file:

  • depends_on ensures the database and cache containers start before the application container, though it does not wait for the service inside them to become ready — use healthchecks for that.
  • env_file loads all variables from .env into the container environment, while environment allows per-service overrides.
  • internal: true on the backend network prevents containers in that network from reaching the internet directly.
  • Named volumes (e.g., static_files) are managed by Docker and persist across docker-compose down. Bind mounts (e.g., ./data/mysql) are tied to specific host paths.

Step 5: Start the Stack

# Pull all images first (optional but useful to spot pull errors early)
docker-compose pull

# Start all services in detached mode
docker-compose up -d

Check that all containers are running:

docker-compose ps

Step 6: View Logs

Compose aggregates logs from all services, colour-coded by service name:

# All services
docker-compose logs -f

# Single service
docker-compose logs -f db

# Last 100 lines from the app
docker-compose logs --tail=100 app

Step 7: Scale Services

With Compose v3 and the deploy.replicas key (used primarily with Docker Swarm), or using the --scale flag for standalone Compose:

# Run 3 replicas of the app service
docker-compose up -d --scale app=3

Note that when scaling, the app service must not use a fixed container_name, and port mappings must not be static. Remove container_name and use a range or omit host-side port mapping for services you intend to scale.

Step 8: Common Day-to-Day Commands

# Stop all services (containers stopped, not removed)
docker-compose stop

# Stop and remove containers, networks (volumes preserved)
docker-compose down

# Stop, remove containers AND named volumes (data loss!)
docker-compose down -v

# Restart a single service after config change
docker-compose restart app

# Rebuild image and recreate container
docker-compose up -d --build app

# Execute a command inside a running container
docker-compose exec db mysql -u root -p

# View resource usage
docker-compose top

Step 9: Override Files for Multiple Environments

Compose supports multiple files merged at startup. Create a docker-compose.override.yml for development tweaks that should not appear in production:

cat > docker-compose.override.yml <<'EOF'
version: "3.8"
services:
  app:
    environment:
      - APP_ENV=development
      - DEBUG=true
    ports:
      - "8080:8080"
EOF

Compose automatically merges this file when you run docker-compose up. For a production deployment, specify only the base file:

docker-compose -f docker-compose.yml up -d

Conclusion

Docker Compose dramatically simplifies multi-service application management on RHEL 7. With a single docker-compose.yml you capture the entire runtime topology of your application — services, networks, volumes, and environment configuration — in a version-controllable text file. Combined with .env files for secrets, healthchecks for dependency readiness, and override files for environment-specific settings, Compose provides a robust foundation for deploying containerised applications on a single RHEL 7 host, and its compose file format transfers directly to Docker Swarm or Kubernetes when you need to scale beyond one machine.