Difficulty: Intermediate
How does Docker Compose orchestrate multi-container applications? Explain Docker networking and service communication.
Docker Compose defines and runs multi-container applications using a YAML file. Each service runs in its own container but they share a network, making inter-service communication seamless.
Docker networking: Compose automatically creates a bridge network for each project. Containers on the same network can reach each other using service names as hostnames. For example, a Node.js container connects to PostgreSQL using db:5432 where db is the service name.
Network types: - Bridge (default): Containers on same host communicate via service names. Isolated from the host network. - Host: Container shares the host network stack. No port mapping needed but no network isolation. - None: No networking. Complete isolation. - Overlay: Multi-host networking for Docker Swarm/Kubernetes.
Volumes persist data beyond container lifecycle. Named volumes are managed by Docker and survive container recreation. Bind mounts map host paths into containers, useful for development hot-reloading.
Health checks let Compose and orchestrators know if a service is actually ready, not just running. depends_on with health checks ensures proper startup order.
# docker-compose.yml
services:
app:
build:
context: ./server
dockerfile: Dockerfile
ports:
- '3000:3000'
environment:
DATABASE_URL: postgresql://user:pass@db:5432/mydb
REDIS_URL: redis://cache:6379
NODE_ENV: production
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
networks:
- backend
restart: unless-stopped
client:
build:
context: ./client
dockerfile: Dockerfile
ports:
- '80:80'
depends_on:
- app
networks:
- backend
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
interval: 5s
timeout: 5s
retries: 5
networks:
- backend
cache:
image: redis:7-alpine
volumes:
- redisdata:/data
networks:
- backend
volumes:
pgdata:
redisdata:
networks:
backend:
driver: bridge
Services communicate using service names as hostnames (db:5432, cache:6379). Health checks on db ensure the app does not start until PostgreSQL is actually ready to accept connections.
# List networks
docker network ls
# NETWORK ID NAME DRIVER
# abc123 bridge bridge
# def456 myapp_backend bridge
# Inspect a network
docker network inspect myapp_backend
# Shows all containers on the network with their IPs
# Create a custom network
docker network create --driver bridge my-network
# Connect a running container to a network
docker network connect my-network container-name
# DNS resolution between containers
# Inside the app container:
ping db # resolves to db container's IP
ping cache # resolves to cache container's IP
# Port mapping:
# -p host:container
# -p 3000:3000 maps host 3000 to container 3000
# -p 8080:3000 maps host 8080 to container 3000
# -p 127.0.0.1:3000:3000 only accessible from localhost
# Internal-only services (no port mapping)
# db and cache don't need ports mapped to host
# They're only accessible from other containers on the network
# Expose ports between containers (no host mapping)
expose:
- "5432"
Docker's built-in DNS resolves service names to container IPs automatically on bridge networks. Only map ports to the host for services that need external access.
# docker-compose.yml (base)
services:
app:
image: myapp
environment:
NODE_ENV: production
# docker-compose.override.yml (dev - auto-loaded)
services:
app:
build: .
volumes:
- ./src:/app/src # hot reload
- /app/node_modules # don't override node_modules
environment:
NODE_ENV: development
command: npm run dev
# docker-compose.prod.yml (production)
services:
app:
image: registry.example.com/myapp:latest
deploy:
replicas: 3
resources:
limits:
cpus: '0.5'
memory: 512M
restart: always
# Run development (uses override automatically)
docker compose up
# Run production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# Useful commands
docker compose ps # list running services
docker compose logs -f app # follow app logs
docker compose exec app sh # shell into app container
docker compose down --volumes # stop and remove volumes
docker compose build --no-cache # rebuild without cache
Use override files for dev/prod differences. The override file auto-loads for development. Production compose adds replicas, resource limits, and uses pre-built images.
Docker Compose, Docker Networking, Service Discovery, Volumes, Health Checks