Перейти к содержимому

26. Деплой на сервер

Деплой Docker-контейнеров в production отличается от разработки: необходимы стабильность, возможность отката, мониторинг и минимальный downtime. Рассмотрим основные стратегии и инструменты.

В простейшем случае production деплой — это запуск контейнера с явными параметрами:

Окно терминала
docker run -d \
--name myapp \
--restart=always \ # автоперезапуск при падении
-p 80:3000 \
-e NODE_ENV=production \
-e DATABASE_URL=postgres://... \
--memory=512m \
--cpus=1 \
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
myapp:v1.2.3

Политики перезапуска:

  • no — не перезапускать (по умолчанию)
  • always — всегда перезапускать
  • on-failure[:N] — при ненулевом коде, максимум N раз
  • unless-stopped — как always, но не после docker stop

Для production рекомендуется unless-stopped: контейнер стартует с демоном Docker, но вы можете его остановить вручную.

Для приложений с несколькими сервисами используйте Compose:

docker-compose.prod.yml
services:
app:
image: myapp:${APP_VERSION} # версия из переменной окружения
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000" # только localhost, не наружу
environment:
- NODE_ENV=production
env_file:
- .env.production
deploy:
resources:
limits:
memory: 512M
cpus: '1'
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3

Деплой:

Окно терминала
# Обновить образ и перезапустить
APP_VERSION=v1.2.3 docker compose -f docker-compose.prod.yml up -d --pull always
# Только пересоздать изменившиеся сервисы
docker compose up -d --no-deps app

Разделяйте конфигурацию по средам с помощью множественных Compose-файлов:

docker-compose.yml # базовая конфигурация
docker-compose.dev.yml # переопределения для разработки
docker-compose.prod.yml # переопределения для production
docker-compose.test.yml # для тестирования
Окно терминала
# Разработка
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Пример базовой и dev-конфигурации:

# docker-compose.yml (базовая)
services:
app:
build: .
environment:
- NODE_ENV=development
# docker-compose.prod.yml (переопределение)
services:
app:
image: registry.example.com/myapp:${VERSION}
environment:
- NODE_ENV=production
restart: unless-stopped

Blue-green деплой исключает downtime: поднимаете новую версию (green) рядом со старой (blue), затем переключаете трафик.

Окно терминала
# Текущая версия (blue) работает на порту 3000
docker run -d --name app-blue -p 3000:3000 myapp:v1.0.0
# Поднимаем новую версию (green) на порту 3001
docker run -d --name app-green -p 3001:3000 myapp:v1.1.0
# Проверяем green
curl http://localhost:3001/health
# Переключаем Nginx
# В nginx.conf: proxy_pass http://localhost:3001;
nginx -s reload
# Убираем старую версию
docker stop app-blue && docker rm app-blue

С Docker Compose и Nginx upstream можно автоматизировать переключение без изменения конфига.

Rolling update обновляет контейнеры по одному, не останавливая весь сервис:

Окно терминала
# Скачать новый образ
docker pull myapp:v1.1.0
# Обновить и пересоздать только сервис app
docker compose up -d --no-deps --build app
# Docker Swarm: встроенные rolling updates
docker service update \
--image myapp:v1.1.0 \
--update-parallelism 1 \
--update-delay 30s \
--update-failure-action rollback \
myapp-service
Окно терминала
# Тегируйте образы конкретными версиями!
docker pull myapp:v1.0.0
# С Compose
APP_VERSION=v1.0.0 docker compose up -d
# Docker Swarm
docker service rollback myapp-service
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1

Для graceful shutdown обрабатывайте SIGTERM в вашем приложении:

Node.js
process.on('SIGTERM', async () => {
console.log('Shutting down gracefully...');
await server.close();
await db.disconnect();
process.exit(0);
});
Окно терминала
# Остановка с таймаутом (по умолчанию 10 секунд)
docker stop --time=30 myapp