core code helper

Docker Compose для локальної розробки: healthcheck, профілі та override

Як описати застосунок з базою, кешем і чергою так, щоб новий розробник запускав усе однією командою, а сервіси стартували в правильному порядку.

0 імпортували Імпортувати собі

«У мене працює» часто означає «у мене стоїть Postgres 14, а в тебе 17». Docker Compose описує все оточення в одному файлі: застосунок, базу, кеш, чергу — з фіксованими версіями й налаштуваннями. Новий розробник клонує репозиторій і запускає docker compose up.

Базовий compose.yaml

services:
  api:
    build: .
    env_file: .env
    environment:
      DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app
      REDIS_URL: redis://cache:6379/0
    ports:
      - "127.0.0.1:8000:8000"
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started

  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Задайте POSTGRES_PASSWORD у .env}
      POSTGRES_DB: app
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 3s
      retries: 10

  cache:
    image: redis:8-alpine

volumes:
  pgdata:

Що тут важливо:

  • Ім'я сервісу = DNS-ім'я. Застосунок підключається до db:5432, а не до localhost.
  • ${VAR:?повідомлення} — Compose відмовиться стартувати, якщо змінну не задано, і покаже зрозуміле повідомлення.
  • 127.0.0.1:8000:8000 — порт відкритий лише для вашої машини, а не для всієї локальної мережі.
  • Іменований том pgdata — дані бази переживуть docker compose down.

depends_on не чекає готовності — якщо не попросити

Звичайний depends_on: [db] гарантує лише, що контейнер бази запущено, а не що Postgres готовий приймати з'єднання. Застосунок стартує першим, падає з «connection refused» — класика.

condition: service_healthy разом з healthcheck вирішує проблему: Compose чекає, поки перевірка стане зеленою.

Порада

Навіть з healthcheck застосунок має вміти перепідключатися до бази. У продакшені база теж іноді перезапускається — і сервіс не повинен через це падати назавжди.

Override-файли: різні налаштування для різних випадків

Compose автоматично підхоплює compose.override.yaml поруч з основним файлом. Туди зручно класти те, що потрібно лише локально:

# compose.override.yaml — лише для розробки
services:
  api:
    build:
      target: dev
    command: uvicorn app.main:app --host 0.0.0.0 --reload
    volumes:
      - ./app:/srv/app      # код з хоста — зміни видно без перезбірки
    environment:
      LOG_LEVEL: debug

Для інших оточень файли вказують явно:

docker compose -f compose.yaml -f compose.ci.yaml up -d

Профілі: опціональні сервіси

Не кожному розробнику щодня потрібні Kafka, Grafana чи емулятор S3. Позначте їх профілем:

services:
  mailpit:
    image: axllent/mailpit
    ports: ["127.0.0.1:8025:8025"]
    profiles: [mail]

  grafana:
    image: grafana/grafana
    profiles: [observability]
docker compose up -d                          # лише основні сервіси
docker compose --profile mail up -d           # плюс локальна пошта

Watch: автооновлення без bind-mount

Bind-mount коду на Windows і macOS буває повільним. Альтернатива — develop.watch: Compose стежить за файлами й синхронізує або перезбирає контейнер.

services:
  web:
    build: .
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: package.json
docker compose up --watch

Команди на кожен день

docker compose up -d --build        # зібрати і запустити у фоні
docker compose ps                   # стан і healthcheck
docker compose logs -f api          # логи одного сервісу
docker compose exec db psql -U app  # консоль у працюючому контейнері
docker compose run --rm api pytest  # одноразова команда в новому контейнері
docker compose down                 # зупинити (томи залишаються)
docker compose down -v              # зупинити і ВИДАЛИТИ томи з даними
Обережно

down -v видаляє іменовані томи, тобто локальну базу з усіма даними. Перед цим переконайтесь, що вони вам не потрібні.

Застосунок у контейнері намагається підключитися до бази за адресою localhost:5432 і отримує «connection refused». Чому?

Усередині контейнера localhost — це сам контейнер застосунку, а не хост і не контейнер бази. Треба використовувати ім'я сервісу з compose-файлу: db:5432.

Збережіть статтю собі

Увійдіть, щоб імпортувати її у власні нотатки, відмічати вивчене, ставити лайки й питати спільноту. Реєстрація безкоштовна: вхід через Google займає кілька секунд.

Увійти через Google Інші способи входу