화. 8월 12th, 2025

G: Are you tired of juggling multiple docker run commands, painstakingly linking containers, and manually setting up environments for your multi-service applications? 🤔 If so, you’re not alone! This is precisely where Docker Compose steps in, transforming a complex deployment dance into a simple, elegant YAML ballet.

This blog post is your ultimate guide to understanding the fundamental building blocks of a docker-compose.yml file. We’ll break down the core structure, explore essential keys, and provide plenty of examples to get you composing like a pro! 🚀


What is Docker Compose? The Orchestra Conductor 🎶

Imagine you have a band. You’ve got a singer, a guitarist, a drummer, and a bassist. Each musician (container) knows how to play their part, but they need someone to coordinate them so they play a coherent song (application).

Docker Compose is that conductor. It’s a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services, networks, and volumes. Then, with a single command, you can spin up or tear down your entire application stack.

In essence, Docker Compose allows you to:

  • Define your entire application stack (web app, database, cache, etc.) in a single file.
  • Start, stop, and rebuild all services with one command.
  • Create isolated environments for your projects.
  • Orchestrate service dependencies and networking.

Why Docker Compose? The Superpowers! ✨

Still not convinced? Let’s look at the superpowers Compose brings to your development workflow:

  1. Simplicity & Speed 💨: No more complex docker run commands with dozens of flags. A simple docker compose up brings your entire app to life.
  2. Reproducibility 🔄: Share your docker-compose.yml file with teammates, and they can spin up the exact same development environment instantly, eliminating “it works on my machine” issues.
  3. Isolated Environments 🌐: Each project can have its own isolated environment with its specific dependencies, preventing conflicts between different projects.
  4. Rapid Development ⚡: Easily make changes to your services, rebuild, and see the effects without manually restarting multiple containers.
  5. Version Control Friendly 🤝: Your entire environment definition lives in a single, version-controlled file.

YAML: The Language of Compose ✍️

Before diving into the docker-compose.yml file, let’s quickly recap YAML (YAML Ain’t Markup Language). It’s a human-friendly data serialization standard, perfect for configuration files.

Key YAML Rules to Remember:

  • Indentation is Crucial! 🚨 YAML uses whitespace (spaces, NOT tabs) for structure. Consistent indentation defines parent-child relationships.
    parent:
      child:
        grandchild_key: value
  • Key-Value Pairs: Data is represented as key: value.
    name: MyService
    port: 8080
  • Lists/Arrays: Denoted by a hyphen (-).
    - item_one
    - item_two

    Or as a block:

    ports:
      - "80:80"
      - "443:443"
  • Strings: Usually don’t need quotes unless they contain special characters or could be misinterpreted (e.g., boolean values like on, off, yes, no).

The Core: docker-compose.yml Basic Structure 🏗️

A typical docker-compose.yml file (or compose.yaml in newer versions) is structured around a few top-level keys.

Here’s the skeleton:

version: '3.8' # Or another version like '3.9', '3'

services:
  # Define your application services here

volumes:
  # Define named volumes for data persistence

networks:
  # Define custom networks for inter-service communication

Let’s break down each section:

1. version: The Blueprint’s Edition 📐

This is the first and most crucial line in your Compose file. It specifies the Compose file format version you’re using. Different versions support different features and syntax.

  • Recommendation: Use 3.8 or 3.9 for modern applications, as they offer the latest features and stability.
  • Example:
    version: '3.8'

2. services: The Heartbeat of Your Application ❤️

This is where you define the individual components (containers) of your application. Each service represents a single container that Compose will manage.

Under services, you’ll list each service name as a top-level key. For example, web, db, redis, etc.

services:
  web:
    # Configuration for your web service
  database:
    # Configuration for your database service
  cache:
    # Configuration for your cache service

Let’s dive into common configurations within a service:

  • image vs. build (Source of Your Container 📦)

    • image: Pulls a pre-built image from a Docker registry (like Docker Hub). Use this when you don’t need to build a custom image.
      services:
        web:
          image: nginx:latest # Pulls the latest Nginx image
        db:
          image: postgres:14 # Pulls PostgreSQL version 14
    • build: Tells Compose to build an image from a Dockerfile in your project. Use this when you have custom application code.
      • context: The path to the directory containing the Dockerfile and other build context.
      • dockerfile: (Optional) The name of the Dockerfile, if it’s not named Dockerfile.
        services:
        my-app:
        build:
          context: ./app # Looks for a Dockerfile in the 'app' directory
          dockerfile: Dockerfile.dev # Uses this specific Dockerfile
        another-service:
        build: ./worker # Looks for Dockerfile in the 'worker' directory
  • ports (Exposing Your App 🚪) Maps ports from the host machine to the container. This allows external access to your service.

    • Syntax: HOST_PORT:CONTAINER_PORT
    • Example:
      services:
        web:
          image: nginx
          ports:
            - "80:80"   # Host port 80 maps to container port 80 (HTTP)
            - "443:443" # Host port 443 maps to container port 443 (HTTPS)
        my-api:
          build: ./api
          ports:
            - "3000:3000" # Host port 3000 maps to container port 3000
  • environment (Setting Up Variables 🌍) Passes environment variables into the container. Crucial for configuration, database credentials, API keys, etc.

    • Can be an array of strings (KEY=VALUE) or a map (KEY: VALUE).
    • Best Practice: For sensitive info, use a .env file and reference variables like ${DB_PASSWORD}.
      services:
      my-app:
      build: ./app
      environment:
        - NODE_ENV=development
        - API_KEY=${MY_API_KEY} # Fetches from .env file
        DATABASE_URL: postgres://user:password@db:5432/mydb
        PORT: 8080
  • volumes (Data Persistence & Sharing 💾) Mounts host paths or named volumes into the container, allowing data to persist even if the container is removed, or to share files between host and container.

    • Syntax: HOST_PATH:CONTAINER_PATH (for bind mounts) or VOLUME_NAME:CONTAINER_PATH (for named volumes).
    • Bind Mounts: Directly links a directory from your host machine. Good for development (code changes reflect instantly).
    • Named Volumes: Docker manages the volume on the host. Ideal for database data or persistent storage. (These are defined separately under the top-level volumes key).

      services:
      web:
      image: nginx
      volumes:
        - ./nginx.conf:/etc/nginx/nginx.conf # Bind mount: custom Nginx config
        - ./html:/usr/share/nginx/html      # Bind mount: serve static files from host
      
      db:
      image: postgres
      volumes:
        - db_data:/var/lib/postgresql/data # Named volume: persist database data
  • depends_on (Service Order 🔗) Expresses dependencies between services. Compose will start services in dependency order. Important: depends_on only ensures the start order, not that the dependent service is ready or healthy. For production, use health checks.

    services:
      web:
        build: ./web
        ports:
          - "80:80"
        depends_on:
          - api # 'api' service will start before 'web'
      api:
        build: ./api
        ports:
          - "3000:3000"
        depends_on:
          - db # 'db' service will start before 'api'
      db:
        image: postgres
  • networks (Custom Network Connectivity 🚦) Connects a service to one or more user-defined networks. By default, Compose creates a default network for your services, allowing them to communicate by service name. Custom networks provide better isolation and organization.

    
    services:
      frontend:
        build: ./frontend
        networks:
          - web_network
      backend:
        build: ./backend
        networks:
          - web_network
          - db_network
      database:
        image: mongo
        networks:
          - db_network

networks: # Defined at the top-level web_network: db_network: external: false

driver: bridge # default driver

# name: my-custom-db-network # optional: custom network name
# ipam:
#   config:
#     - subnet: 172.20.0.0/16
```
  • container_name (Custom Container Name 🏷️) Assigns a specific name to the container instead of Compose generating one (e.g., my-app-web-1).

    services:
      web:
        image: nginx
        container_name: my_nginx_server # Easier to identify
  • restart (Auto-Restart Policy ♻️) Defines when to restart 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’s explicitly stopped.
    • unless-stopped: Always restart unless explicitly stopped by the user.
      services:
      my-app:
      build: ./app
      restart: unless-stopped # Recommended for most services

3. volumes: Managing Persistent Data 💾

This top-level key is where you define named volumes. These are managed by Docker and are the preferred way to persist data generated by or used by Docker containers. They are explicitly created and managed by Compose and appear in docker volume ls.

volumes:
  db_data:      # A named volume called 'db_data'
  app_logs:     # Another named volume for application logs

You can then reference these named volumes within your services section, as shown in the volumes example above (db_data:/var/lib/postgresql/data).

4. networks: Custom Communication Channels 🌐

This top-level key allows you to define custom networks for your services. By default, Compose creates a single bridge network for all services in your docker-compose.yml file, and services can communicate with each other using their service names (e.g., ping database).

Custom networks provide better isolation, allowing you to segment your application into different layers (e.g., a frontend_network and a backend_network).

networks:
  app_internal: # A custom network for internal service communication
  app_public:   # Another custom network, maybe for services exposed to the outside
    driver: bridge # Explicitly set the driver (bridge is default)

You then assign services to these networks using the networks key within each service definition.


Putting It All Together: A Comprehensive Example 🧑‍💻

Let’s create a docker-compose.yml for a simple web application consisting of:

  • A Node.js backend API (listening on port 3000)
  • A MongoDB database
  • An Nginx reverse proxy (serving static files and forwarding requests to the API)

Project Structure:

my-app/
├── docker-compose.yml
├── backend/
│   └── Dockerfile
│   └── app.js
│   └── package.json
├── frontend/
│   └── Dockerfile
│   └── index.html
│   └── script.js
└── nginx/
    └── nginx.conf

docker-compose.yml:

version: '3.8'

services:
  nginx:
    image: nginx:latest
    ports:
      - "80:80" # Map host port 80 to container port 80 for Nginx
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf # Bind mount custom Nginx config
    depends_on:
      - backend # Nginx needs the backend to be available (for proxying)
    networks:
      - web_network # Connect Nginx to the web network

  backend:
    build: ./backend # Build the image from the ./backend directory
    ports:
      - "3000" # Expose container port 3000 to other services (not host)
    environment:
      NODE_ENV: production
      MONGO_URI: mongodb://database:27017/myapp # Connect to 'database' service
    volumes:
      - backend_data:/app/data # Named volume for backend data/logs if needed
    depends_on:
      - database # Backend needs the database to be available
    networks:
      - web_network # Connect backend to the web network
      - db_network  # Connect backend to the database network

  database:
    image: mongo:latest # Use the official MongoDB image
    volumes:
      - db_data:/data/db # Persist MongoDB data using a named volume
    networks:
      - db_network # Connect database only to the database network
    restart: unless-stopped # Keep the database running

volumes:
  db_data: {}     # Define the 'db_data' named volume
  backend_data: {} # Define the 'backend_data' named volume

networks:
  web_network: {} # Define a custom network for web-facing services
  db_network: {}  # Define a custom network for database communication

Running Your Compose Application 🚀

Once your docker-compose.yml is ready, running your application is incredibly simple!

  1. Navigate to your project directory (where docker-compose.yml is located) in your terminal.

  2. Start all services in detached mode (background):

    docker compose up -d
    • -d (detached mode) runs containers in the background, freeing up your terminal.
  3. Check the status of your services:

    docker compose ps

    This will show you which containers are running and their exposed ports.

  4. View logs for all services:

    docker compose logs
    • To follow logs in real-time: docker compose logs -f
    • To view logs for a specific service: docker compose logs backend
  5. Stop and remove all services, networks, and volumes (optional, if specified):

    docker compose down
    • To remove volumes as well: docker compose down -v (Use with caution!)

Best Practices & Pro Tips ✨

  • Small, Focused Services: Design your services to do one thing well. This makes them easier to manage, scale, and debug.
  • Use .env files for Sensitive Data: Never hardcode credentials in your docker-compose.yml. Use a .env file (e.g., DB_PASSWORD=mysecret) and reference variables using ${VAR_NAME}.
  • Version Control Your docker-compose.yml: Treat it like code. Commit it to Git along with your application code.
  • Comments are Your Friend: Explain complex sections or non-obvious configurations within your YAML file using # for comments.
  • Start Simple, Add Complexity: Don’t try to cram everything into your first Compose file. Get the basic services running, then gradually add volumes, networks, and advanced configurations.
  • Read the Official Docs: The Docker Compose documentation is excellent and always up-to-date.

Conclusion: Your Composing Journey Begins! 🎉

You’ve now got a solid understanding of the basic structure of a docker-compose.yml file. You know about version, services, volumes, and networks, and the crucial configurations within services like image, build, ports, environment, and volumes.

Docker Compose simplifies multi-container application development and deployment immensely. It’s a powerful tool that will save you countless hours and headaches. So, go forth and start composing your Dockerized applications with confidence! Happy coding! 🐳

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다