Docker Compose в продакшне: best practices для небольшого бизнеса
Я Семёнов Евгений Сергеевич, директор АйТи Фреш. Когда к нам обращаются клиенты с «у нас docker compose, но после каждого обновления всё падает» — в 90% случаев проблема в стартовом compose-файле, который написали для теста и не доработали. У нас на практике через Docker Compose работают десятки клиентских инфраструктур. Делюсь тем, что обязано быть в каждом боевом compose-файле.
Структура проекта
В продакшне compose-файл живёт не один. У меня всегда минимум три:
docker-compose.yml— база, общая для всех сред.docker-compose.override.yml— локальная разработка, bind-mounts, дебаг.docker-compose.prod.yml— продакшн-оверрайды: лимиты, рестарты, логирование.
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-файле. Варианты по возрастанию безопасности:
.envрядом с compose,chmod 600, в git-е игнорим.env_file:с отдельным файлом для каждого сервиса.docker secrets(только в Swarm mode) — пароль виден как файл внутри контейнера.- Внешний 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
- Фиксированные теги образов (
postgres:16.2, неlatest). restart: unless-stoppedна долгоживущих сервисах.healthcheckна всём, от чего кто-то зависит.mem_limitиcpusу каждого сервиса.- Ротация логов или отправка в центральный коллектор.
- Переменные окружения в
.env, не в YAML. - Разделённые сети (frontend / backend).
- Регулярные dump-бэкапы баз + тесты восстановления.
HEALTHCHECKв Dockerfile, если своё приложение.USERв Dockerfile, не root.
Приведём ваш 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.