DEV Community

Cover image for A Beginner's Guide to Docker Compose for Developers
Suleiman Dibirov
Suleiman Dibirov

Posted on

A Beginner's Guide to Docker Compose for Developers

If you're a developer who's been using Docker containers but finding yourself typing long docker run commands or managing multiple containers manually, it's time to level up your workflow with Docker Compose. This guide will walk you through the basics of Docker Compose and show you how to simplify your development environment setup.

What is Docker Compose?

Docker Compose is a tool that helps you define and share multi-container applications. With Compose, you use a YAML file to configure your application's services, networks, and volumes. Then, with a single command, you create and start all the services from your configuration.

Why Should You Use Docker Compose?

  • Simplified Configuration: Define your entire application stack in a single file
  • Reproducible Environments: Share your application setup with other developers
  • Easy Testing: Create isolated testing environments quickly
  • Version Control: Track changes to your application environment alongside your code

Getting Started

First, ensure you have both Docker and Docker Compose installed. Docker Desktop for Windows and Mac comes with Compose, but Linux users might need to install it separately.

The compose.yml File

The heart of Docker Compose is the compose.yml file (or docker-compose.yml for backward compatibility). Here's a simple example:

services:
  web:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/app
    environment:
      - NODE_ENV=development

  db:
    image: postgres:13
    environment:
      - POSTGRES_USER=myuser
      - POSTGRES_PASSWORD=mypassword
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
Enter fullscreen mode Exit fullscreen mode

Let's break down each configuration element:

  1. services: The top-level key that defines all your application's containers/services

    • Each indented key under services (like web and db) is a service name you choose
    • Service names are used for network aliases and container prefixes
  2. build: Specifies the build configuration for creating a container image

    • . means build from Dockerfile in the current directory
    • Can also specify a path to a directory containing a Dockerfile
    • Can be expanded to include build context and arguments:
     build:
       context: ./dir
       dockerfile: Dockerfile.dev
       args:
         ENV: development
    
  3. ports: Maps container ports to host ports

    • Format is "HOST_PORT:CONTAINER_PORT"
    • Example: "3000:3000" maps container port 3000 to host port 3000
    • Can specify multiple port mappings
    • Can use port ranges: "3000-3005:3000-3005"
  4. volumes: Defines mounted volumes for data persistence and file sharing

    • Bind mounts: ./host/path:/container/path maps a host directory to container
    • Named volumes: volume_name:/container/path uses a Docker-managed volume
    • Anonymous volumes: /container/path creates an unnamed Docker-managed volume
    • Can specify read-only volumes: ./host/path:/container/path:ro
  5. environment: Sets environment variables inside the container

    • Can be a list: - KEY=value
    • Or a map:
     environment:
       NODE_ENV: development
       API_KEY: secret
    
  6. image: Specifies a pre-built Docker image to use

    • Can be from Docker Hub: postgres:13
    • Or private registry: registry.example.com/image:tag
    • Used when you don't need to build an image locally
  7. Named volumes declaration:

    • The top-level volumes key declares named volumes used in services
    • These volumes persist even when containers are removed
    • Can specify volume drivers and options:
     volumes:
       postgres_data:
         driver: local
         driver_opts:
           type: none
           device: /path/on/host
           o: bind
    

Common additional configurations you might need:

services:
  web:
    # Restart policy
    restart: always  # or 'no', 'on-failure', 'unless-stopped'

    # Dependencies
    depends_on:
      - db
      - redis

    # Custom container name
    container_name: myapp_web

    # Additional hosts entries
    extra_hosts:
      - "host.docker.internal:host-gateway"

    # Resource limits
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M

    # Health check
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000"]
      interval: 30s
      timeout: 10s
      retries: 3
Enter fullscreen mode Exit fullscreen mode

These configurations allow you to define complex, multi-container applications in a declarative way. The compose file serves as both documentation and executable configuration, making it easier to share and maintain your application's infrastructure requirements.

Essential Commands

Here are the Docker Compose commands you'll use frequently:

# Start your services
docker compose up

# Run in detached mode
docker compose up -d

# Stop services (keeps containers)
docker compose stop

# Stop and remove containers, networks
docker compose down

# Stop and remove containers, networks, volumes
docker compose down -v

# View logs
docker compose logs

# View logs for specific service
docker compose logs service_name

# Follow logs
docker compose logs -f

# Rebuild services
docker compose build

# Execute command in a running container
docker compose exec service_name command
# Example: docker compose exec web npm test

# Run a one-off command
docker compose run --rm service_name command
# Example: docker compose run --rm web npm install

# List running containers
docker compose ps

# Check service status
docker compose top

# Pull latest images
docker compose pull
Enter fullscreen mode Exit fullscreen mode

Real-World Example: MERN Stack

Let's look at a practical example using a MERN (MongoDB, Express, React, Node.js) stack:

services:
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    environment:
      - REACT_APP_API_URL=http://localhost:5000
    depends_on:
      - backend

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    ports:
      - "5000:5000"
    volumes:
      - ./backend:/app
      - /app/node_modules
    environment:
      - MONGODB_URI=mongodb://mongodb:27017/myapp
    depends_on:
      - mongodb

  mongodb:
    image: mongo:latest
    ports:
      - "27017:27017"
    volumes:
      - mongodb_data:/data/db

volumes:
  mongodb_data:
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Use Version Control: Always include your compose file in version control
  2. Environment Variables: Use .env files for sensitive data:
services:
  web:
    env_file:
      - .env
Enter fullscreen mode Exit fullscreen mode
  1. Named Volumes: Use named volumes instead of bind mounts for databases:
volumes:
  - mongodb_data:/data/db  # Good
  - ./data:/data/db       # Not recommended for databases
Enter fullscreen mode Exit fullscreen mode
  1. Dependencies: Use depends_on to manage service startup order:
services:
  web:
    depends_on:
      - db
      - redis
Enter fullscreen mode Exit fullscreen mode

Common Issues and Solutions

1. Port Conflicts

If you see "port is already allocated" errors:

  • Check for running containers using the same ports
  • Use docker compose ps to list running containers
  • Change the port mapping in your compose file

2. Volume Permissions

If you encounter permission issues:

  • Ensure proper ownership of mounted volumes
  • Consider using user directive in your service definition:
services:
  web:
    user: "1000:1000"
Enter fullscreen mode Exit fullscreen mode

Conclusion

Docker Compose is an invaluable tool for modern development workflows. It simplifies container management, makes development environments reproducible, and helps teams work more efficiently. Start with simple configurations and gradually add more features as your needs grow.

Remember: the goal is to make development easier, not more complicated. Don't over-engineer your Compose files – keep them simple and focused on your immediate needs.


Happy containerizing! 🐳

Top comments (0)