Docker Compose: многоконтейнерные приложения

Зачем Docker Compose

docker run запускает один контейнер. Реальное приложение — это минимум три: бэкенд, база данных, кэш. Запускать их вручную — пытка. Docker Compose решает это одной командой.

bash
docker compose up -d    # запустить всё
docker compose down     # остановить всё
docker compose logs -f  # логи всех сервисов

Структура docker-compose.yml

yaml
services:
  web:          # имя сервиса (используется как hostname)
    image: nginx:alpine
    ports:
      - "80:80"
 
  api:
    build: ./backend    # собрать из Dockerfile
    environment:
      DATABASE_URL: postgres://user:pass@db:5432/myapp
    depends_on:
      - db
 
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data
 
volumes:
  pgdata:

build: vs image:

yaml
services:
  # Готовый образ из registry
  redis:
    image: redis:7-alpine
 
  # Собрать из Dockerfile в текущей папке
  api:
    build: .
 
  # Собрать из конкретной папки и файла
  api:
    build:
      context: ./backend
      dockerfile: Dockerfile.prod
      args:
        NODE_ENV: production

Переменные окружения

yaml
# Вариант 1: прямо в YAML (не для секретов!)
environment:
  PORT: 3000
  NODE_ENV: production
 
# Вариант 2: из .env файла
env_file:
  - .env
  - .env.local
 
# Вариант 3: подстановка из shell
environment:
  API_KEY: ${MY_API_KEY}   # из переменной окружения хоста
bash
# .env файл
DATABASE_URL=postgres://user:pass@db:5432/myapp
SECRET_KEY=super-secret

depends_on: порядок запуска

yaml
services:
  api:
    depends_on:
      db:
        condition: service_healthy  # ждать health check
      redis:
        condition: service_started  # просто запущен
 
  db:
    image: postgres:16-alpine
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

depends_on гарантирует порядок запуска, но не готовность к работе. Используй condition: service_healthy для надёжности.

Volumes

yaml
services:
  db:
    volumes:
      - pgdata:/var/lib/postgresql/data    # named volume
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql  # bind mount
 
  app:
    volumes:
      - .:/app         # весь проект (для разработки)
      - /app/node_modules  # анонимный volume (исключить node_modules хоста)
 
volumes:
  pgdata:    # объявить named volume

Networks

yaml
networks:
  frontend:
  backend:
    internal: true  # не доступна снаружи
 
services:
  nginx:
    networks: [frontend]
 
  api:
    networks: [frontend, backend]  # мост между зонами
 
  db:
    networks: [backend]   # только внутренняя сеть

Сервисы в одной сети обращаются друг к другу по имени: http://api:8000, postgres://db:5432.

Полный стек для разработки

yaml
# docker-compose.yml
services:
  web:
    build: ./frontend
    ports:
      - "3000:3000"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    environment:
      VITE_API_URL: http://localhost:8000
    depends_on: [api]
 
  api:
    build: ./backend
    ports:
      - "8000:8000"
    volumes:
      - ./backend:/app
    environment:
      DATABASE_URL: postgres://user:pass@db:5432/dev
      REDIS_URL: redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
 
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: dev
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user"]
      interval: 5s
      retries: 5
 
  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
 
volumes:
  pgdata:
  redis-data:

Команды

bash
# Жизненный цикл
docker compose up -d          # запустить в фоне
docker compose up --build     # пересобрать перед запуском
docker compose down           # остановить и удалить контейнеры
docker compose down -v        # + удалить volumes
docker compose restart api    # перезапустить конкретный сервис
 
# Мониторинг
docker compose ps             # состояние сервисов
docker compose logs -f api    # логи сервиса
docker compose top            # процессы внутри контейнеров
 
# Выполнение команд
docker compose exec api bash        # войти в контейнер
docker compose exec db psql -U user # запустить команду
docker compose run --rm api pytest  # одноразовый контейнер
 
# Обновление
docker compose pull           # обновить образы
docker compose up -d --force-recreate  # пересоздать контейнеры

Override файлы

yaml
# docker-compose.yml — базовый
services:
  api:
    image: myapp:latest
    environment:
      NODE_ENV: production
yaml
# docker-compose.override.yml — автоматически накладывается при разработке
services:
  api:
    build: .     # переопределяет image
    volumes:
      - .:/app
    environment:
      NODE_ENV: development
    ports:
      - "8000:8000"
bash
# Явно указать файлы
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Profiles: не все сервисы нужны всегда

yaml
services:
  api:
    # нет profile — запускается всегда
 
  pgadmin:
    image: dpage/pgadmin4
    profiles: [tools]    # только при явном включении
 
  tests:
    build: .
    command: pytest
    profiles: [ci]
bash
docker compose up                      # только api
docker compose --profile tools up      # + pgadmin
docker compose --profile ci run tests  # запустить тесты

Compose vs Swarm vs Kubernetes

ComposeSwarmKubernetes
Масштаб1 хостНесколько хостовТысячи хостов
СложностьНизкаяСредняяВысокая
СценарийРазработка, маленький продСредний продКрупный прод
Rolling updatesНетЕстьЕсть

Для большинства проектов Compose (разработка) + Swarm или managed Kubernetes (прод) — оптимальный выбор.

Интерактивные уроки по теме
Docker Compose: многоконтейнерные приложения+100 XP →Volumes: хранение данных в Docker+90 XP →Сети Docker Compose: как сервисы общаются+90 XP →
Готов учиться интерактивно?

Двигай слайдеры и наблюдай как работает математика

Начать учиться →