Environment Management
Docker Compose & Multi-Service Apps
Define and run multi-container applications — app, database, cache, and workers — with a single YAML file.
What Docker Compose Is
Real applications rarely run as a single service. A typical web app needs a server, a database, a cache, and maybe a background worker. Docker Compose lets you define all of these services and run them together with a single command: docker compose up.
Instead of manually starting four containers with four separate commands and configuring how they connect, you write one docker-compose.yml file that describes everything.
The docker-compose.yml Structure
version: '3.9'
services:
# Your Next.js application
web:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- REDIS_URL=redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
# PostgreSQL database
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
# Redis cache
cache:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:Key Concepts
Services — Each service is a container. Each gets its own image, configuration, and network identity.
Volumes — Named volumes persist data across container restarts. Without a volume, your database loses all data when the container stops.
Networks — By default, all services in a Compose file share a network. Services communicate using their service name as the hostname. Notice the database URL uses db as the host — that is the service name.
depends_on — Ensures services start in order and can wait for health checks before considering a dependency ready.
Development Workflow
For local development, mount your source code as a volume for hot-reloading:
services:
web:
build:
context: .
target: development
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
command: npm run devThis mounts your local files into the container, so code changes are reflected immediately without rebuilding.
Essential Compose Commands
# Start all services (build if needed)
docker compose up
# Start in background
docker compose up -d
# Stop all services
docker compose down
# Stop and remove volumes (resets databases)
docker compose down -v
# View logs for all services
docker compose logs -f
# View logs for one service
docker compose logs -f web
# Run a one-off command in a service container
docker compose exec web sh
# Rebuild images before starting
docker compose up --buildCompose Profiles
Use profiles to define optional services that don't run by default:
services:
web:
build: .
monitoring:
image: grafana/grafana
profiles:
- monitoring
adminer:
image: adminer
profiles:
- dev-toolsStart with optional services: docker compose --profile monitoring up
Key Takeaways
- Docker Compose defines multi-service applications in a single YAML file — run everything with one command
- Services communicate by service name (not localhost) over Docker's internal network
- Named volumes persist data across container restarts — without them, your database resets every time
- Volume mounts in development enable hot-reloading without rebuilding the image
- Compose is for development and small deployments; Kubernetes is for large-scale production orchestration
Example
# docker-compose.yml for a full-stack app
version: '3.9'
services:
web:
build: .
ports: ["3000:3000"]
environment:
DATABASE_URL: postgresql://postgres:password@db:5432/myapp
REDIS_URL: redis://cache:6379
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 5
cache:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data: