Docker Compose в продакшене на одном VPS: как сделать правильно
Практика4 минРедакция Innovialabs
Коротко
Docker Compose на одном VPS вполне подходит для продакшена, если настроить автоперезапуск, healthcheck, ротацию логов, HTTPS через reverse proxy, регулярные бэкапы томов и понятную процедуру обновления.
Можно ли использовать Compose в продакшене
Да. Для небольших и средних проектов один VPS с Docker Compose — простой и надёжный вариант: вся инфраструктура описана в одном файле, её легко воспроизвести и перенести. Главное — не запускать «как на ноутбуке», а закрыть несколько обязательных пунктов.
Политика перезапуска
Без неё контейнер не поднимется после падения или перезагрузки сервера.
services:
app:
image: registry.example.com/app:1.4.2
restart: unless-stopped
unless-stopped— перезапуск всегда, кроме случаев, когда вы остановили контейнер вручную. Хороший выбор по умолчанию.always— поднимет даже то, что вы остановили руками, после рестарта Docker.on-failure— только при ненулевом коде выхода.
Также проверьте, что сам Docker стартует при загрузке: systemctl enable docker.
Секреты и env-файлы
Не храните пароли в docker-compose.yml и в Git. Вынесите их в .env или отдельный файл:
services:
app:
env_file: .env.production
Файл с секретами — права 600, добавлен в .gitignore. В репозитории держите .env.example с пустыми значениями, чтобы было видно, какие переменные нужны.
Healthcheck
restart реагирует только на падение процесса. Если приложение зависло, но процесс жив, поможет healthcheck:
services:
app:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
worker:
depends_on:
db:
condition: service_healthy
Учтите: в обычном Docker без Swarm статус unhealthy сам по себе контейнер не перезапускает. Он нужен для depends_on, мониторинга и ваших скриптов. Утилита curl должна быть в образе — или используйте другую проверку.
Ротация логов
По умолчанию драйвер json-file пишет логи без ограничения, и однажды диск заполнится. Задайте лимиты:
x-logging: &logging
driver: json-file
options:
max-size: "10m"
max-file: "5"
services:
app:
logging: *logging
Можно задать то же глобально в /etc/docker/daemon.json, но это применится только к новым контейнерам.
Reverse proxy и HTTPS
Не публикуйте порт приложения наружу. Наружу смотрят только 80 и 443 у reverse proxy, а приложение доступно лишь во внутренней сети Compose.
Варианты:
| Прокси | Чем удобен |
|---|---|
| Caddy | получает и продлевает сертификаты Let's Encrypt автоматически, минимальный конфиг |
| Traefik | настраивается через labels контейнеров, сам находит сервисы |
| Nginx + certbot | привычный и гибкий, но продление сертификатов настраивается отдельно |
Если всё же нужен порт для отладки, привяжите его к localhost: "127.0.0.1:3000:3000". Помните, что Docker пишет свои правила iptables и опубликованный порт может обойти UFW.
Обновления без долгого простоя
Используйте конкретные теги образов, а не latest — так понятно, что запущено, и легко откатиться.
Порядок обновления:
docker compose pull app
docker compose up -d app
docker image prune -f
Сначала скачивается новый образ, пока старый контейнер работает, затем контейнер пересоздаётся — простой сводится к времени старта приложения. Если и это критично, запустите две копии за прокси и обновляйте их по очереди. Миграции базы данных делайте обратно совместимыми, чтобы старая и новая версии кода могли работать с одной схемой.
Бэкапы томов
Данные живут в volumes, и они не переживут гибель диска или сервера.
- Базы данных бэкапьте штатными средствами (
pg_dump,mysqldump), а не копированием файлов работающей БД. - Файлы из томов архивируйте и отправляйте за пределы сервера — в объектное хранилище или на другую машину.
- Запускайте бэкап по расписанию (cron или systemd timer) и следите за ошибками.
- Периодически проверяйте восстановление на чистом сервере: непроверенный бэкап — это надежда, а не бэкап.
Когда одного сервера мало
Пора думать о нескольких серверах или оркестраторе, если:
- простой при обслуживании или сбое железа стал недопустим;
- нагрузку уже не вытянуть вертикальным ростом VPS;
- нужно независимо масштабировать отдельные сервисы;
- команда часто выкатывает изменения и нужны канареечные релизы.
Промежуточный шаг — вынести базу данных в управляемый сервис, а приложение запускать на двух серверах за балансировщиком.