📝 Docker

Docker Compose: Advanced Features

P
Author
Pyland
📅
Published
30.06.2026
⏱️
Reading time
3 min
👁️
Views
75
🌳
Level
Advanced

A basic docker-compose.yml is just the starting point. Production workloads need healthchecks, profiles, override files, and resource management.

healthcheck for depends_on

By default depends_on only guarantees that a container has started, not that the service inside it is ready. Postgres may be starting up but not yet accepting connections. The solution is healthcheck.

services:
  db:
    image: postgres:15
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s       # how often to check
      timeout: 3s        # timeout for a single check
      retries: 5         # number of retries before marking unhealthy
      start_period: 10s  # grace period before the first check

  web:
    build: .
    depends_on:
      db:
        condition: service_healthy   # wait for the healthcheck to pass

Available condition values:
- service_started — container is running (default)
- service_healthy — healthcheck passed
- service_completed_successfully — exited with code 0 (for init containers)

Healthcheck examples for common services:

# Redis
healthcheck:
  test: ["CMD", "redis-cli", "ping"]
  interval: 5s
  timeout: 3s
  retries: 3

# HTTP service
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
  interval: 10s
  timeout: 5s
  retries: 3
  start_period: 30s

profiles: Dev and Prod Configurations

Profiles let you define services that only start in specific scenarios.

services:
  web:
    build: .
    ports:
      - "8000:8000"
    # No profile — always starts

  db:
    image: postgres:15
    # No profile — always starts

  pgadmin:
    image: dpage/pgadmin4
    profiles: [dev]          # dev only
    ports:
      - "5050:80"

  mailhog:
    image: mailhog/mailhog
    profiles: [dev]          # dev only
    ports:
      - "8025:8025"

  nginx:
    image: nginx:alpine
    profiles: [prod]         # prod only
    ports:
      - "80:80"
      - "443:443"
# Start only the base services
docker compose up -d

# Start with the dev profile
docker compose --profile dev up -d

# Multiple profiles
docker compose --profile dev --profile monitoring up -d

Override: docker-compose.override.yml

Docker Compose automatically loads docker-compose.override.yml and merges it with the main file. This is great for per-developer local settings.

# docker-compose.yml — shared, committed to git
services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      LOG_LEVEL: info

  db:
    image: postgres:15
# docker-compose.override.yml — local, in .gitignore
services:
  web:
    volumes:
      - .:/app          # live reload: mount source code into the container
    environment:
      LOG_LEVEL: debug
      DEBUG: "true"
    command: python manage.py runserver 0.0.0.0:8000

  db:
    ports:
      - "5432:5432"     # in dev: postgres reachable from the host

Specifying files explicitly:

# Use specific files
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# Main file only (skip the automatic override)
docker compose -f docker-compose.yml up -d

Variables and ${VAR} Interpolation

# docker-compose.yml
services:
  web:
    image: ${REGISTRY:-docker.io}/${IMAGE_NAME}:${IMAGE_TAG:-latest}
    ports:
      - "${APP_PORT:-8000}:8000"
    deploy:
      replicas: ${WEB_REPLICAS:-1}

Substitution syntax:
- ${VAR} — variable value; error if unset
- ${VAR:-default} — value or default if unset
- ${VAR:?error message} — error with a message if unset

# Preview the final configuration after substitution
docker compose config

deploy: Replicas and Resources

The deploy section controls resource allocation (relevant for Docker Swarm and Compose v3+; partially applies in local Compose as well).

services:
  web:
    build: .
    deploy:
      replicas: 3               # number of container copies
      update_config:
        parallelism: 1          # update one replica at a time
        delay: 10s              # pause between replicas
      restart_policy:
        condition: on-failure
        max_attempts: 3
      resources:
        limits:
          cpus: "0.5"           # at most 50% of one CPU core
          memory: 512M          # at most 512 MB of RAM
        reservations:
          cpus: "0.1"
          memory: 128M

  db:
    image: postgres:15
    deploy:
      resources:
        limits:
          memory: 1G
        reservations:
          memory: 256M

Resource limits are important protection against a single container consuming all available host memory.

Management Commands

# Starting
docker compose up -d                   # start in the background
docker compose up -d --build           # rebuild images first
docker compose up -d web db            # specific services only

# Stopping
docker compose down                    # stop and remove containers
docker compose down -v                 # also remove volumes
docker compose down --rmi local        # also remove local images
docker compose stop web                # stop a service without removing it

# Logs
docker compose logs -f                 # all logs in real time
docker compose logs -f web             # logs for a specific service
docker compose logs --tail=100 web     # last 100 lines

# Running commands
docker compose exec web bash           # open a shell in the container
docker compose exec web python manage.py migrate
docker compose run --rm web pytest     # run a one-off container

# Scaling
docker compose up -d --scale web=3    # run 3 copies of web

# Information
docker compose ps                      # status of all services
docker compose top                     # processes inside containers
docker compose images                  # service images
docker compose port web 8000           # find the mapped port

Full Example: Production-like Stack

version: "3.8"

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      web:
        condition: service_healthy
    profiles: [prod]

  web:
    build: .
    image: ${IMAGE_NAME:-myapp}:${IMAGE_TAG:-latest}
    environment:
      DATABASE_URL: postgresql://${DB_USER}:${DB_PASS}@db:5432/${DB_NAME}
      SECRET_KEY: ${SECRET_KEY}
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 20s
    deploy:
      replicas: ${WEB_REPLICAS:-2}
      resources:
        limits:
          cpus: "1.0"
          memory: 512M

  db:
    image: postgres:15
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASS}
      POSTGRES_DB: ${DB_NAME}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      interval: 5s
      timeout: 3s
      retries: 5
    deploy:
      resources:
        limits:
          memory: 1G

volumes:
  pgdata:

Your reaction to the article

💬 Comments (0)

🔐 Sign in to leave a comment
🚪 Login
💭

No comments yet

Be the first to share your opinion about this article!

🔗 Similar

Similar articles

Continue learning with these materials

📝

Deploying FastAPI with Docker

Railway will also automatically detect a Dockerfile if one is present.

📅 30.06.2026 👁️ 81
📝

Multi-stage Builds: Shrinking Your Docker Image

A large image means slow downloads, more disk usage, and a bigger attack surface. Multi-stage...

📅 30.06.2026 👁️ 79
📝

Docker Networking: How Containers Communicate

Containers are isolated, but they often need to talk to each other and to the...

📅 30.06.2026 👁️ 77

Did you like the article?

Subscribe to our updates and receive new articles first. Grow with PyLand!