Фиксируйте версии образов:
Тег latest на проде это мина. Сегодня за ним стоит одна версия, после очередного docker compose pull уже другая, и сервис ведёт себя не так, как вчера. Указывайте точный тег, а свои образы собирайте в CI и тегируйте номером версии или хэшем коммита.
Код: Выделить всё
# плохо
image: nginx:latest
# хорошо
image: nginx:1.28.0
image: cr.yandex/crp9f3k2/shop-api:1.4.2
Контейнер должен сам подниматься после падения процесса и после перезагрузки сервера, за это отвечает restart: unless-stopped. Но рестарт не спасает, когда процесс жив, а приложение зависло. Для этого есть healthcheck. Соберём типичный прод-сервис целиком:
Код: Выделить всё
services:
api:
image: cr.yandex/crp9f3k2/shop-api:1.4.2
restart: unless-stopped
stop_grace_period: 30s
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Логи без max-size за пару месяцев забивают диск, /var/lib/docker разрастается до десятков гигабайт. Лимит можно задать и глобально в /etc/docker/daemon.json, после правки нужен systemctl restart docker, и настройка коснётся только новых контейнеров.
Код: Выделить всё
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
При docker stop контейнер получает SIGTERM, а через 10 секунд (или stop_grace_period) уже SIGKILL. Чтобы приложение успело закрыть соединения и дописать данные, нужны два условия: оно обрабатывает SIGTERM в коде и реально его получает. Со вторым часто подводит shell-форма CMD:
Код: Выделить всё
# shell-форма: PID 1 будет /bin/sh, сигнал до node не дойдёт
CMD node server.js
# exec-форма: node сам PID 1 и получит SIGTERM
CMD ["node", "server.js"]
Файл .env с паролями живёт только на сервере, с правами 600, и обязательно прописан в .gitignore и .dockerignore. Помните из главы 7: переменные окружения видны через docker inspect любому, у кого есть доступ к демону. Для чувствительных систем смотрите в сторону Vault или docker secrets. Всё ценное храните в named volumes (глава 5) и бэкапьте: контейнер расходный материал, том нет. Базы бэкапьте штатными средствами вроде pg_dump, а не копированием каталога у живого постгреса.
Типичные грабли:
Сборка образа прямо на боевом сервере. Билд отъедает CPU и память у работающих сервисов и плохо воспроизводится. Собирайте в CI или хотя бы на отдельной машине, на прод только docker compose pull.
docker compose down -v. Флаг -v сносит тома вместе с базой. На проде эту комбинацию лучше не набирать вообще.
Открытые наружу порты. Запись ports: "5432:5432" вешает постгрес на все интерфейсы, причём Docker правит iptables напрямую и спокойно обходит ufw. Внутренним сервисам публикация портов не нужна вовсе, они общаются по сети Compose (глава 6). Если порт нужен только локально, привязывайте явно: "127.0.0.1:5432:5432".
Root в контейнере. Про USER и read-only мы говорили в главе 11. Перед выкаткой проверьте, что это не осталось "на потом".
Что усвоили:
Прод отличается от локалки дисциплиной: точные теги, рестарт-политики, healthcheck, лимиты ресурсов, ротация логов, корректное завершение, закрытые порты и бэкапы томов. На этом курс закончен. Дальше идут оркестрация (Docker Swarm или Kubernetes) и автоматизация выкаток через CI/CD, но и без них вы уже способны уверенно держать на Docker небольшой боевой проект.