· 17 мин чтения

Docker Compose в продакшне: best practices для небольшого бизнеса

Я Семёнов Евгений Сергеевич, директор АйТи Фреш. Когда к нам обращаются клиенты с «у нас docker compose, но после каждого обновления всё падает» — в 90% случаев проблема в стартовом compose-файле, который написали для теста и не доработали. У нас на практике через Docker Compose работают десятки клиентских инфраструктур. Делюсь тем, что обязано быть в каждом боевом compose-файле.

Структура проекта

В продакшне compose-файл живёт не один. У меня всегда минимум три:

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Такой подход даёт один источник истины + чёткое разделение «где что».

Restart policy — никаких unless-stopped на всё подряд

В каждом сервисе должен быть restart. Но не бездумный always, иначе сломанный контейнер будет перезапускаться в петле и жечь CPU.

ПолитикаКогда использовать
noОдноразовые init-контейнеры
on-failure[:N]Джобы, которые могут упасть по ошибке
unless-stoppedОбычные сервисы — автозапуск при рестарте хоста, но не при ручном stop
alwaysКритичные службы (reverse proxy), но с осторожностью

Healthcheck — без этого compose не знает, живы ли вы

Без healthcheck Docker считает контейнер «здоровым», если процесс не упал. Проблема: PostgreSQL внутри ещё инициализируется, а зависимый сервис уже ломится с коннектами.

services:
  db:
    image: postgres:16-alpine
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  api:
    image: myapp:1.4.2
    depends_on:
      db:
        condition: service_healthy

Я всегда ставлю start_period — он не считает неудачи первые N секунд, пока сервис просыпается.

Ресурсы: лимиты и резервы

Без лимитов один сервис с утечкой памяти кладёт весь хост. Задаём явно.

services:
  api:
    image: myapp:1.4.2
    mem_limit: 1g
    mem_reservation: 512m
    cpus: "1.5"
    pids_limit: 200
    restart: unless-stopped

На практике: выдайте сервису в 1.5 раза больше, чем он обычно ест в пике. Это страховка от короткого всплеска, но не даст ему укатить соседей.

Логирование с ротацией

Если не ограничить логи, они съедят диск за неделю. У меня было: 1С-шлюз писал 2 ГБ/день в json-file, через две недели забил 40 ГБ раздел. Решение:

services:
  api:
    image: myapp:1.4.2
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "5"
        compress: "true"

Лучше — отправляйте логи в централизованный стек (Loki, ELK) через syslog/gelf-драйвер. Но базовая ротация должна быть всегда.

Секреты и переменные окружения

Никогда не храните пароли в самом compose-файле. Варианты по возрастанию безопасности:

  1. .env рядом с compose, chmod 600, в git-е игнорим.
  2. env_file: с отдельным файлом для каждого сервиса.
  3. docker secrets (только в Swarm mode) — пароль виден как файл внутри контейнера.
  4. Внешний Vault с подтягиванием на старте через скрипт-обёртку.
# .env
DB_PASSWORD=StrongPassword123!

# docker-compose.yml
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}

Сети и изоляция

Не держите все сервисы в одной сети. Разделяйте публичные и внутренние:

networks:
  frontend:
  backend:
    internal: true

services:
  web:
    image: nginx:alpine
    networks: [frontend, backend]
    ports: ["80:80", "443:443"]
  api:
    image: myapp:1.4.2
    networks: [backend]
  db:
    image: postgres:16
    networks: [backend]

База и API не торчат наружу, nginx — единственная точка входа.

Реальный кейс: магазин автозапчастей, 160 заказов в день

Однажды в 2024 году взяли на сопровождение интернет-магазин автозапчастей — 160 заказов в день, 22 сотрудника в офисе в Мытищах. У них был старый compose без healthcheck, лимитов и ротации логов. После каждого обновления платёжный модуль падал, потому что база не успевала подняться. За 3 рабочих дня мы переписали compose под продакшн-правила: добавили healthcheck во все сервисы, лимиты 2 ГБ на каждый контейнер, вынесли логи в Loki на наш сервер Dell Xeon Platinum 8280.

Стоимость — 62 000 руб. Результат: за следующие 3 месяца ни одного падения при деплоях, время обновления с 15 минут простоя до 10 секунд. Клиент остался у нас на абонентском обслуживании.

Обновления без даунтайма

Для простых сервисов (stateless API за прокси):

# В compose.yml
services:
  api:
    image: myapp:${TAG}
    deploy:
      replicas: 2
    restart: unless-stopped

# Обновляем
TAG=1.4.3 docker compose up -d --no-deps api

Compose пересоздаст контейнеры по очереди, если перед ними стоит traefik или nginx с upstream-ами — запросы не потеряются.

Бэкапы томов

Бэкапить /var/lib/docker/volumes целиком — плохая идея, потому что файлы базы могут быть в неконсистентном состоянии. Правильно — dump внутри контейнера:

# Бэкап Postgres
docker exec pg pg_dumpall -U postgres | gzip > /backup/pg-$(date +%F).sql.gz

# Бэкап MySQL
docker exec mysql mysqldump --all-databases -uroot -p$MYSQL_ROOT_PASSWORD | \
  gzip > /backup/mysql-$(date +%F).sql.gz

# Файлы — просто rsync с тома
rsync -a /var/lib/docker/volumes/userdata/_data/ /backup/userdata/

Автоматизируйте через cron + проверяйте восстановление раз в квартал, иначе бэкап — это только вера в бэкап.

Что обязательно должно быть в prod-compose

Приведём ваш compose в боевое состояние

У нас на 8 серверах Dell Xeon Platinum 8280 в дата-центре МТС Москва крутится больше 200 контейнеров клиентов. Переведём ваши сервисы на продакшн-стандарт: healthcheck, лимиты, логи, бэкапы, мониторинг. 15+ лет опыта администрирования корпоративных инфраструктур.

Телефон: +7 903 729-62-41
Telegram: @ITfresh_Boss
Семёнов Евгений Сергеевич, директор АйТи Фреш

FAQ — Docker Compose в продакшне

Можно ли использовать docker compose в продакшне или нужен Kubernetes?
Для одного-двух хостов и десятка сервисов compose — отличный выбор. K8s имеет смысл от трёх нод и 30+ сервисов, где нужно автомасштабирование и мульти-узловая отказоустойчивость. У большинства малого бизнеса compose закрывает 90% задач.
Как переносить секреты в compose?
Не храните пароли в compose-файле. Используйте .env с ограниченными правами (chmod 600), docker secrets для Swarm, либо внешний Vault. Никогда не коммитьте .env в git — добавьте в .gitignore.
Нужно ли задавать лимиты памяти и CPU?
Да, обязательно. Без лимитов утекающий сервис съедает весь хост. Минимум: mem_limit или deploy.resources.limits.memory. Для CPU — cpus: '1.5', например.
Как делать zero-downtime deploy на compose?
Compose умеет rolling-update через docker compose up -d с новым тегом — контейнер пересоздаётся с даунтаймом 1–3 секунды. Для полного zero-downtime ставьте nginx/traefik перед двумя инстансами и переключайте.
Как бэкапить данные в volumes?
Простой способ — остановить контейнер и скопировать /var/lib/docker/volumes/имя/_data. Для баз — dump внутри контейнера: docker exec pg pg_dumpall -U postgres > dump.sql. Автоматизируйте через cron или restic.

Подпишитесь на рассылку ITfresh

Раз в неделю — практические гайды для руководителя IT и сисадмина: безопасность, 1С, миграции, резервные копии, лайфхаки из реальных проектов.

Реквизиты оператора персональных данных

ООО «АЙТИ-ФРЕШ», ИНН 7719418495, КПП 771901001. Юридический адрес: 105523, г. Москва, Щёлковское шоссе, д. 92, корп. 7. Контакт: info@itfresh.ru, +7 903 729-62-41. Оператор обрабатывает e-mail подписчика в целях рассылки информационных и рекламных материалов до момента отзыва согласия.