ν™”. 8μ›” 12th, 2025

G: Are you tired of juggling multiple docker run commands for your multi-service applications? Do you wish for a simpler, more reproducible way to define and run your entire development stack? If so, then Docker Compose is your knight in shining armor! πŸ›‘οΈ

This comprehensive guide will take you on a journey to master Docker Compose, from understanding its core YAML syntax to orchestrating complex multi-container environments and preparing them for deployment. Let’s dive in!


1. Why Docker Compose? The Multi-Container Challenge 🧠

Imagine your application consists of a web server, a database, and a caching layer. Without Docker Compose, you’d be running something like this:

docker run -d --name my-db -e POSTGRES_PASSWORD=mysecretpassword postgres:13
docker run -d --name my-cache redis:latest
docker run -d --name my-web -p 80:5000 --link my-db:database --link my-cache:cache my-web-app:latest

This quickly becomes cumbersome:

  • Manual Linking: -link is deprecated and hard to manage.
  • Port Mapping: Remembering which port maps to what.
  • Environment Variables: Long and error-prone.
  • Reproducibility: How do you share this setup with teammates or deploy it consistently?

Enter Docker Compose! πŸŽ‰ Docker Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.

Key Benefits:

  • Declarative Configuration: Define your entire application stack in a single, readable YAML file.
  • Reproducibility: Ensure everyone on your team (and your CI/CD pipeline) runs the exact same environment.
  • Simplified Management: Start, stop, and rebuild your entire application with simple commands.
  • Networking Made Easy: Services automatically discover each other by their names within the Compose network.

2. The Heart of Docker Compose: docker-compose.yml πŸ’–

The magic happens in a file typically named docker-compose.yml (or docker-compose.yaml). This file uses YAML syntax, which is designed to be human-readable.

Let’s break down the core structure:

version: '3.8' # Specifies the Compose file format version
services:    # Defines the different services (containers) in your application
  web:
    # Service configuration for 'web'
  database:
    # Service configuration for 'database'
volumes:     # Defines named volumes for persistent data
  db_data:
networks:    # Defines custom networks for service communication
  app_network:

2.1. version

The version key specifies the Compose file format version. Always use the latest stable version (currently 3.8 or 3.9) for access to the newest features and best practices.

2.2. services

This is the core. Each key under services defines a separate container (or service) that will be part of your application.

2.3. volumes

Used to define named volumes, which are the recommended way to persist data generated by your Docker containers. This ensures your data isn’t lost when containers are removed or recreated.

2.4. networks

Allows you to define custom bridge networks for your services. While Compose creates a default network for all services, custom networks offer better isolation and organization for complex applications.


3. Diving Deep into services Configuration 🧩

Each service within your docker-compose.yml file can have a wealth of configuration options. Let’s explore the most common and crucial ones:

3.1. image vs. build (Source of Your Container) πŸ—οΈ

  • image: Pull an existing Docker image from Docker Hub or a private registry.
    services:
      database:
        image: postgres:13-alpine # Uses an official PostgreSQL image
  • build: Build an image from a Dockerfile in a specified context (directory).
    services:
      web:
        build: . # Looks for a Dockerfile in the current directory
        # OR specify context and Dockerfile name:
        # build:
        #   context: ./webapp
        #   dockerfile: Dockerfile.dev

    πŸ’‘ Tip: For development, build is common. For production, you often build images separately and then use image in your production Compose file.

3.2. ports (Exposing Your Services) πŸšͺ

Maps ports from the host machine to the container. Format: HOST_PORT:CONTAINER_PORT.

services:
  web:
    ports:
      - "80:80"       # Map host port 80 to container port 80 (standard HTTP)
      - "8080:5000"   # Map host port 8080 to container port 5000 (e.g., for Flask app)
      - "443:443/tcp" # Specify protocol (TCP is default, but good for clarity)
      - "53:53/udp"   # For UDP services

⚠️ Caution: Be mindful of port conflicts if multiple applications on your host use the same ports.

3.3. environment (Configuration Variables) βš™οΈ

Sets environment variables inside the container. Essential for database credentials, API keys, etc.

services:
  database:
    image: postgres:13-alpine
    environment:
      POSTGRES_DB: mydatabase
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mysecretpassword
  web:
    environment:
      API_KEY: your_api_key_here
      DATABASE_URL: postgres://myuser:mysecretpassword@database:5432/mydatabase # 'database' is the service name!

πŸ’‘ Tip: Use an .env file for sensitive data or environment-specific variables (see Advanced Concepts).

3.4. volumes (Persistent Storage) πŸ’Ύ

Mounts host paths or named volumes into the container for data persistence or sharing files.

  • Bind Mounts: HOST_PATH:CONTAINER_PATH (useful for development, e.g., source code).
    services:
      web:
        volumes:
          - "./app:/app" # Mounts local 'app' directory into container's '/app'
  • Named Volumes: VOLUME_NAME:CONTAINER_PATH (recommended for production data persistence).
    services:
      database:
        volumes:
          - db_data:/var/lib/postgresql/data # Mounts the 'db_data' named volume
    volumes: # Defined at the top level
      db_data:

    βœ… Best Practice: Always use named volumes for database data or anything you want to persist reliably.

3.5. networks (Service Communication) 🌐

Connects a service to specific networks. By default, services are on a single “default” network, but custom networks offer more control.

services:
  web:
    networks:
      - app_network # Connects 'web' service to 'app_network'
  database:
    networks:
      - app_network # Connects 'database' service to 'app_network'
networks: # Defined at the top level
  app_network:
    driver: bridge # Default, but can be specified

You can also set network aliases, allowing services to be discoverable by multiple names within a network:

services:
  database:
    networks:
      app_network:
        aliases:
          - my_sql_db # 'web' can connect to 'database' or 'my_sql_db'

3.6. depends_on (Startup Order) πŸ”—

Specifies that a service depends on other services. This ensures that the dependent services are started before the current service.

services:
  web:
    depends_on:
      - database
      - cache
  database:
    # ...
  cache:
    # ...

⚠️ Important Note: depends_on only ensures the order of startup, not that the dependent service is ready (e.g., database fully initialized and accepting connections). For readiness, use healthcheck or wait-for-it scripts in your entrypoint.

3.7. command & entrypoint (Overriding Defaults) ➑️

  • command: Overrides the default command defined in the Docker image.
  • entrypoint: Overrides the default entrypoint defined in the Docker image. Often used for custom startup scripts.
    services:
    web:
    image: myapp:latest
    command: ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"]
    worker:
    image: myapp:latest
    entrypoint: ["/usr/local/bin/python", "worker.py"]

3.8. restart (Resilience) πŸ”„

Defines the restart policy for the container.

  • no: Do not automatically restart.
  • on-failure: Restart only if the container exits with a non-zero exit code.
  • always: Always restart, even if it stops gracefully.
  • unless-stopped: Always restart unless explicitly stopped or Docker daemon is stopped.
    services:
    web:
    restart: always

3.9. healthcheck (Readiness Checks) β€οΈβ€πŸ©Ή

Defines commands to check if a service is healthy and ready to accept connections. Essential for production-like setups.

services:
  web:
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost/health || exit 1"]
      interval: 30s # Check every 30 seconds
      timeout: 10s  # Timeout if no response in 10 seconds
      retries: 3    # Retry 3 times before considering unhealthy
      start_period: 20s # Give the container 20s to start up before checking

4. Essential Docker Compose Commands ⌨️

Once your docker-compose.yml is ready, here are the commands you’ll use most often. Note that docker-compose (with a hyphen) is the legacy command; the modern docker compose (with a space) is now integrated into the Docker CLI. We’ll use the modern form.

  • docker compose up: Build, create, and start your services.

    • docker compose up: Starts all services in the foreground.
    • docker compose up -d: Starts all services in detached (background) mode.
    • docker compose up --build: Forces rebuilding of images defined by build: before starting. Essential when you change your Dockerfile.
    • docker compose up --scale web=3: Scales the ‘web’ service to 3 instances (only useful if your app supports it).
  • docker compose down: Stop and remove containers, networks, and volumes (if not named).

    • docker compose down: Stops and removes containers and default networks.
    • docker compose down -v: Also removes named volumes (use with caution, as this deletes persistent data!).
    • docker compose down --rmi all: Removes images as well.
  • docker compose ps: List the running services and their status.

    docker compose ps
  • docker compose logs: View the logs of your services.

    • docker compose logs: Shows logs for all services.
    • docker compose logs -f web: Follows (streams) logs for the ‘web’ service.
    • docker compose logs --tail 100: Shows the last 100 lines of logs.
  • docker compose exec: Run a command inside a running service container.

    docker compose exec web bash # Open a bash shell inside the 'web' container
    docker compose exec database psql -U myuser mydatabase # Connect to PostgreSQL
  • docker compose build: Builds or rebuilds services. Useful if you only want to build images without starting containers.

    docker compose build web # Build only the 'web' service image
  • docker compose restart: Restarts services.

    docker compose restart web # Restart only the 'web' service

5. Practical Example: Flask Web App with PostgreSQL πŸ’‘

Let’s put everything together with a common scenario: a Python Flask web application that connects to a PostgreSQL database.

Project Structure:

my_flask_app/
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ app.py
β”‚   └── requirements.txt
β”œβ”€β”€ Dockerfile
└── docker-compose.yml

1. app/requirements.txt

Flask==2.3.2
psycopg2-binary==2.9.9

2. app/app.py A simple Flask app to connect to the database and show a message.

import os
from flask import Flask
import psycopg2

app = Flask(__name__)

@app.route('/')
def hello_world():
    db_host = os.environ.get('DB_HOST', 'localhost')
    db_name = os.environ.get('DB_NAME', 'mydatabase')
    db_user = os.environ.get('DB_USER', 'myuser')
    db_password = os.environ.get('DB_PASSWORD', 'mysecretpassword')

    try:
        conn = psycopg2.connect(
            host=db_host,
            database=db_name,
            user=db_user,
            password=db_password
        )
        cursor = conn.cursor()
        cursor.execute("SELECT version();")
        db_version = cursor.fetchone()[0]
        cursor.close()
        conn.close()
        return f"Hello from Flask! Connected to PostgreSQL: {db_version}"
    except Exception as e:
        return f"Hello from Flask! Error connecting to DB: {e}"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

3. Dockerfile (for the Flask app)

# Use an official Python runtime as a parent image
FROM python:3.9-slim-buster

# Set the working directory in the container
WORKDIR /app

# Copy the requirements file into the working directory
COPY app/requirements.txt .

# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application code
COPY app/ .

# Expose the port the app runs on
EXPOSE 5000

# Run the application
CMD ["python", "app.py"]

4. docker-compose.yml

version: '3.8'

services:
  web:
    build: . # Build from the current directory (where Dockerfile is)
    ports:
      - "80:5000" # Map host port 80 to container port 5000 (Flask default)
    environment:
      # These variables will be available inside the 'web' container
      DB_HOST: db # 'db' is the service name of our database
      DB_NAME: mydatabase
      DB_USER: myuser
      DB_PASSWORD: mysecretpassword
    depends_on:
      # Ensures 'db' service starts before 'web' service
      - db
    networks:
      - app_network
    restart: always # Keep the web app running

  db:
    image: postgres:13-alpine # Use a lightweight PostgreSQL image
    environment:
      # PostgreSQL specific environment variables
      POSTGRES_DB: mydatabase
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mysecretpassword
    volumes:
      # Persist PostgreSQL data to a named volume
      - db_data:/var/lib/postgresql/data
    networks:
      - app_network
    restart: always

volumes:
  db_data: # Define the named volume for database persistence

networks:
  app_network: # Define a custom bridge network
    driver: bridge

How to Run It:

  1. Navigate to the my_flask_app directory in your terminal.
  2. Run: docker compose up --build -d
    • up: Starts the services.
    • --build: Ensures the web service’s image is built (or rebuilt if Dockerfile changed).
    • -d: Runs services in detached mode (background).
  3. Check status: docker compose ps
  4. View logs: docker compose logs -f (or docker compose logs -f web for specific service)
  5. Access the app: Open your browser and go to http://localhost/ (or http://127.0.0.1/). You should see a message confirming the connection to PostgreSQL.
  6. Stop and clean up: docker compose down (use docker compose down -v if you want to remove the database data for a clean start).

6. Advanced Concepts & Deployment Considerations πŸš€

While Docker Compose is primarily a development tool, understanding these concepts helps in preparing for production environments.

6.1. Environment Variables with .env Files 🀫

For sensitive information (like passwords) or environment-specific values, it’s best to keep them out of your docker-compose.yml file. Docker Compose automatically looks for a .env file in the same directory. .env file:

POSTGRES_PASSWORD=reallySecurePassword!

docker-compose.yml:

services:
  db:
    image: postgres:13-alpine
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # References the variable from .env

πŸ’‘ Tip: Add .env to your .gitignore to prevent committing sensitive data.

6.2. Multiple Compose Files for Different Environments πŸŒ“

You can use multiple Compose files to customize your application for different environments (e.g., development, testing, production).

  • docker-compose.yml: Base configuration (common to all environments).
  • docker-compose.override.yml: Overrides or extends the base file (automatically loaded by docker compose up).
  • docker-compose.prod.yml: Production-specific configurations.

Example: docker-compose.yml (Base):

services:
  web:
    build: .
    ports:
      - "80:5000"
    # ...

docker-compose.override.yml (Dev-specific):

services:
  web:
    volumes:
      - ./app:/app # Bind mount for live code changes
    environment:
      FLASK_DEBUG: 1 # Enable Flask debug mode

To run both: docker compose up (it automatically merges docker-compose.yml and docker-compose.override.yml).

To run a specific set (e.g., base + production): docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

6.3. Scaling Services πŸ“ˆ

You can scale services horizontally using the --scale flag with docker compose up:

docker compose up --scale web=3 -d

This will start three instances of your web service. Ensure your application is designed to be stateless for effective scaling.

6.4. Orchestration for Production ☁️

While Docker Compose is excellent for local development and CI/CD pipelines, it’s generally not recommended as a full-fledged production orchestrator for highly available, fault-tolerant applications. For production, consider:

  • Docker Swarm: Docker’s built-in orchestration tool. Docker Compose files can often be deployed directly to Swarm.
  • Kubernetes: The industry standard for container orchestration. More complex to set up but offers unparalleled power and flexibility.

Docker Compose serves as an excellent stepping stone and a local development sandbox for these more robust solutions.


Conclusion: Your Docker Compose Conquest is Complete! πŸŽ‰

Congratulations! You’ve navigated the depths of Docker Compose, from understanding its foundational YAML syntax to deploying a practical multi-service application. You now have the power to:

  • Define complex application stacks in a clean, declarative way.
  • Ensure consistency and reproducibility across development environments.
  • Manage multiple containers with single, intuitive commands.
  • Leverage persistent storage and sophisticated networking.

Docker Compose is an indispensable tool in any modern developer’s toolkit. Keep experimenting, keep building, and keep conquering your containerized challenges!

What’s Next?

  • Experiment with more services (e.g., Nginx for a reverse proxy, Redis for caching).
  • Explore docker-compose.override.yml for your own development workflows.
  • Dabble in Docker Swarm or Kubernetes to see how your Compose knowledge translates to production orchestration.

Happy Composing! ✨

λ‹΅κΈ€ 남기기

이메일 μ£Όμ†ŒλŠ” κ³΅κ°œλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. ν•„μˆ˜ ν•„λ“œλŠ” *둜 ν‘œμ‹œλ©λ‹ˆλ‹€