Guides & Tutorials

Docker Compose Tutorial: From Beginner to Pro

Step-by-step guide for Docker Compose. Learn how to easily orchestrate and deploy multi-container applications.

Jonas Hottler
January 26, 2025
13 min read time
DockerDocker ComposeDevOpsContainerTutorialInfrastructure
Docker Compose Tutorial: From Beginner to Pro - Guides & Tutorials | Blog

Docker Compose Tutorial: Everything You Need to Know

Docker Compose is the essential tool for anyone working with multiple Docker containers. This guide takes you from basics to advanced concepts.

What is Docker Compose?

Docker Compose allows you to define and run multi-container Docker applications. Instead of configuring and starting each container individually, you describe your entire application in a single YAML file.

Without Docker Compose:

docker network create myapp docker run -d --name db --network myapp -e POSTGRES_PASSWORD=secret postgres:15 docker run -d --name redis --network myapp redis:7 docker run -d --name app --network myapp -p 3000:3000 myapp:latest

With Docker Compose:

# docker-compose.yml services: db: image: postgres:15 environment: POSTGRES_PASSWORD: secret redis: image: redis:7 app: build: . ports: - "3000:3000"

A single docker compose up starts everything.

Installation

macOS and Windows

Docker Compose is integrated into Docker Desktop. After installing Docker Desktop, docker compose is automatically available.

Linux (Ubuntu/Debian)

# Install Docker sudo apt update sudo apt install docker.io # Install Docker Compose Plugin sudo apt install docker-compose-plugin # Verify docker compose version

Your First docker-compose.yml

Create a file named docker-compose.yml:

version: '3.8' services: web: image: nginx:latest ports: - "8080:80" volumes: - ./html:/usr/share/nginx/html

Start with:

docker compose up

Open http://localhost:8080 – done!

Essential Commands

CommandDescription
docker compose upStarts all services
docker compose up -dStarts in background (detached)
docker compose downStops and removes containers
docker compose psShows running services
docker compose logsShows logs of all services
docker compose logs -f webFollows logs of a service
docker compose exec web shOpen shell in container
docker compose buildRebuilds images
docker compose pullPulls latest images

Practical Example: WordPress with MySQL

version: '3.8' services: wordpress: image: wordpress:latest ports: - "8080:80" environment: WORDPRESS_DB_HOST: db WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress_password WORDPRESS_DB_NAME: wordpress volumes: - wordpress_data:/var/www/html depends_on: - db restart: unless-stopped db: image: mysql:8.0 environment: MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress_password MYSQL_ROOT_PASSWORD: root_password volumes: - db_data:/var/lib/mysql restart: unless-stopped volumes: wordpress_data: db_data:

What's happening here?

  • Two services: wordpress and db
  • WordPress waits for MySQL (depends_on)
  • Data persists in named volumes
  • Containers restart on crash (restart: unless-stopped)

Key Concepts in Detail

1. Volumes: Persistent Data Storage

services: db: image: postgres:15 volumes: # Named volume (managed by Docker) - db_data:/var/lib/postgresql/data # Bind mount (host directory) - ./init.sql:/docker-entrypoint-initdb.d/init.sql volumes: db_data:

2. Networks: Connecting Containers

services: frontend: networks: - frontend_net backend: networks: - frontend_net - backend_net database: networks: - backend_net networks: frontend_net: backend_net:

Containers can only communicate if they're in the same network.

3. Environment Variables

services: app: environment: - NODE_ENV=production - API_KEY=${API_KEY} # From .env file env_file: - .env.production

.env file (DON'T commit!):

API_KEY=secret123
DB_PASSWORD=secure456

4. Healthchecks

services: web: image: nginx healthcheck: test: ["CMD", "curl", "-f", "http://localhost"] interval: 30s timeout: 10s retries: 3 start_period: 40s

5. Resource Limits

services: app: deploy: resources: limits: cpus: '0.5' memory: 512M reservations: cpus: '0.25' memory: 256M

Development vs. Production

Development (docker-compose.override.yml)

# docker-compose.yml (base) version: '3.8' services: app: build: . environment: - NODE_ENV=development
# docker-compose.override.yml (automatically merged in dev) version: '3.8' services: app: volumes: - .:/app - /app/node_modules command: npm run dev ports: - "3000:3000" - "9229:9229" # Debugger

Production (docker-compose.prod.yml)

# docker-compose.prod.yml version: '3.8' services: app: image: myapp:latest environment: - NODE_ENV=production restart: always deploy: replicas: 3

Start with:

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

Common Problems and Solutions

Problem: Port already in use

Error: bind: address already in use

Solution:

# Find process on port lsof -i :8080 # Or use different port ports: - "8081:80"

Problem: Container can't reach another

Solution: Services reach each other by service name, not localhost:

# Wrong in app code DATABASE_URL=localhost:5432 # Correct DATABASE_URL=db:5432 # "db" is the service name

Problem: Changes not applied

Solution:

docker compose up --build # Rebuilds images docker compose down -v && docker compose up # Also deletes volumes

Problem: Volumes taking too much space

Solution:

docker system prune -a --volumes # CAUTION: Deletes all unused! docker volume prune # Only unused volumes

Best Practices

  1. Always use .env for secrets – Never commit passwords in docker-compose.yml

  2. Use specific image tagspostgres:15 instead of postgres:latest

  3. Define healthchecks – For better monitoring and depends_on

  4. Set restart policiesunless-stopped or always for production

  5. Set resource limits – Prevent one container from consuming everything

  6. Isolate networks – Don't put everything in the default network

Useful Compose Extensions

Configure Logging

services: app: logging: driver: "json-file" options: max-size: "10m" max-file: "3"

Profiles for Optional Services

services: app: # Always started debug: profiles: ["debug"] # Only with: docker compose --profile debug up testing: profiles: ["test"]

Conclusion

Docker Compose significantly simplifies container orchestration. With a single YAML file, you can define, start, and manage complex multi-container applications.

Next steps:

  • Experiment with the examples
  • Containerize your existing projects
  • Explore Docker Swarm or Kubernetes for larger deployments

For questions about container strategy or DevOps optimization, Balane Tech is happy to help.

Tags

DockerDocker ComposeDevOpsContainerTutorialInfrastructure