Стратегия обновления Docker: обновление контейнеров без простоя на VPS
Четыре метода обновления Docker-контейнеров на VPS по возрастанию сложности — от простого pull-and-replace до blue-green деплоя без простоя с Traefik. Пиннинг образов, откат, уведомления Diun и docker-rollout.
Обновление Docker-контейнеров на VPS не обязательно означает простой. Правильный подход зависит от того, что ты запускаешь и сколько времени простоя можешь себе позволить. Личный блог переживёт пару секунд даунтайма при docker compose up -d. SaaS-продукт с платящими клиентами — нет.
Это руководство охватывает четыре метода, от простейшего до самого надёжного. Каждый следующий строится на предыдущем. Начни с того, что подходит под твою ситуацию, и переходи на следующий уровень, когда понадобится.
Требования: VPS с Debian 12 или Ubuntu 24.04, Docker Engine 27+ и Docker Compose v2. Все команды используют синтаксис плагина docker compose (не устаревший бинарник docker-compose v1). Docker в продакшене на VPS: что ломается и как это починить
Как закрепить Docker-образы на конкретной версии?
Закрепи образы на конкретной minor- или patch-версии в compose-файле. Тег latest — движущаяся цель, которая может подтянуть ломающие изменения без предупреждения. Пиннинг даёт контроль над тем, когда происходят обновления, и позволяет откатиться, сохраняя предыдущий образ локально.
Разные стратегии тегов несут разные риски:
| Формат тега | Пример | Уровень риска | Поведение при обновлении |
|---|---|---|---|
latest |
nginx:latest |
Высокий | Любая версия в любой момент. Непонятно, что изменилось. |
| Только major | nginx:1 |
Средне-высокий | Может перескочить с 1.25 на 1.27. Minor-версии могут менять поведение. |
| Minor | nginx:1.27 |
Низкий | Получает patch-обновления (1.27.0 → 1.27.3). Безопасно для большинства рабочих нагрузок. |
| Patch | nginx:1.27.3 |
Очень низкий | Точная версия. Никаких сюрпризов. Обновляешь вручную. |
| Digest | nginx:1.27.3@sha256:6f12... |
Минимальный | Побайтово идентичный образ каждый раз. Защищён от мутации тегов. |
Для большинства продакшн-сервисов закрепляй на minor-версии (image: postgres:16.6). Это правильный баланс между патчами безопасности и стабильностью. Для сервисов, где важна воспроизводимость (CI, регулируемые среды), закрепляй на полном digest.
services:
app:
image: myapp:2.4.1
# Not: image: myapp:latest
db:
image: postgres:16.6
Запиши digest текущих образов перед обновлением. Они понадобятся для отката:
docker image inspect --format='{{index .RepoDigests 0}}' $(docker compose images app -q)
myapp@sha256:a1b2c3d4e5f6...
Как настроить health check в Docker Compose?
Health check сообщает Docker, действительно ли контейнер работает, а не просто запущен. Все паттерны обновления без простоя зависят от них. Без health check Docker не может узнать, готов ли новый контейнер, прежде чем удалить старый.
Добавь блок healthcheck к каждому сервису в compose-файле. Команда test выполняется внутри контейнера с заданным интервалом. Docker помечает контейнер как healthy только после прохождения теста.
services:
app:
image: myapp:2.4.1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 15s
timeout: 5s
retries: 3
start_period: 30s
Что делает каждое поле:
- test: Команда для выполнения.
CMDзапускает её напрямую. ИспользуйCMD-SHELL, если нужны возможности shell вроде пайпов. - interval: Время между проверками. 15 секунд — разумное значение для веб-сервисов.
- timeout: Сколько ждать завершения команды, прежде чем считать её проваленной.
- retries: Количество последовательных неудач, после которых Docker пометит контейнер как
unhealthy. - start_period: Льготный период после запуска контейнера. Health check во время этого окна не учитываются в пороге отказов. Установи достаточно времени для загрузки приложения.
Для сервисов без установленного curl используй встроенную проверку самого сервиса:
db:
image: postgres:16.6
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myuser -d mydb"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
cache:
image: redis:7.4
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
После запуска сервисов проверь, что health check проходят:
docker compose ps
NAME IMAGE STATUS PORTS
app myapp:2.4.1 Up 2 minutes (healthy) 0.0.0.0:8080->8080/tcp
db postgres:16.6 Up 2 minutes (healthy) 5432/tcp
Статус (healthy) означает, что health check настроен и проходит. Если видишь (health: starting), контейнер ещё в start_period. Если (unhealthy), смотри логи health check:
docker inspect --format='{{json .State.Health}}' $(docker compose ps -q app) | python3 -m json.tool
Как обновить Docker-контейнер на VPS?
Выполни docker compose pull, чтобы скачать новый образ, затем docker compose up -d, чтобы заменить контейнер. Docker Compose останавливает старый контейнер, удаляет его и запускает новый из обновлённого образа. Это вызывает краткое прерывание (обычно 2–10 секунд), пока новый контейнер стартует и проходит health check.
Пошагово: простое обновление
Перед обновлением сделай бэкап volumes. Сломанное обновление с повреждёнными данными куда хуже пары секунд простоя.
Прочитай changelog новой версии. Проверь наличие ломающих изменений, устаревших опций конфигурации и необходимых шагов миграции. Это займёт пять минут, но сэкономит часы отладки.
# Pull the new image
docker compose pull app
# Check what changed
docker compose up -d --dry-run
Флаг --dry-run (Docker Compose v2.20+) показывает, что Compose собирается сделать, не выполняя это на самом деле. Ты увидишь, какие контейнеры будут пересозданы:
DRY RUN MODE - service "app" - Pull
DRY RUN MODE - Container app-1 - Recreate
DRY RUN MODE - Container app-1 - Started
Примени обновление:
docker compose up -d app
[+] Running 1/1
✔ Container app-1 Started 0.8s
Проверь, что новый контейнер healthy:
docker compose ps app
NAME IMAGE STATUS PORTS
app myapp:2.5.0 Up 15 seconds (healthy) 0.0.0.0:8080->8080/tcp
Затем проверь снаружи сервера, что сервис доступен:
curl -s -o /dev/null -w "%{http_code}" https://app.example.com/health
200
Когда обновлять Docker-контейнеры?
Не все обновления одинаково срочны. Единый график обновлений приводит либо к ненужным рискам, либо к пропущенным патчам безопасности.
- Патчи безопасности (CVE): Применяй немедленно. Подпишись на уведомления о безопасности своих образов. Известная CVE в публично доступном контейнере эксплуатируется в течение часов после раскрытия, не дней.
- Patch-версии (например, 2.4.1 → 2.4.2): Планируй еженедельно или раз в две недели. Это баг-фиксы. Читай changelog, обновляй, проверяй.
- Minor-версии (например, 2.4 → 2.5): Планируй ежемесячно. Сначала протестируй в staging-окружении, если оно есть. Проверь changelog на изменения поведения.
- Major-версии (например, 2.x → 3.x): Планируй и тестируй. Major-версии ломают вещи. Читай руководство по миграции. Тестируй на отдельном VPS или локально, прежде чем трогать продакшн.
Как откатить Docker-контейнер на предыдущий образ?
В Docker Compose нет встроенной команды отката. Чтобы вернуться: отредактируй compose-файл, закрепив предыдущий тег или digest образа, затем выполни docker compose up -d. Контейнер перезапустится со старым образом. Это работает, только если старый образ сохранился локально (не запускай docker image prune сразу после обновления).
Пошаговый откат
Допустим, ты обновил myapp с 2.4.1 до 2.5.0, и новая версия сломана.
- Проверь, что старый образ ещё доступен локально:
docker images myapp
REPOSITORY TAG IMAGE ID CREATED SIZE
myapp 2.5.0 abc123def456 2 hours ago 185MB
myapp 2.4.1 789fed654cba 2 weeks ago 182MB
- Отредактируй compose-файл, закрепив предыдущую версию:
services:
app:
image: myapp:2.4.1
- Выполни откат:
docker compose up -d app
[+] Running 1/1
✔ Container app-1 Started 0.7s
- Убедись, что откат прошёл успешно:
docker compose ps app
NAME IMAGE STATUS PORTS
app myapp:2.4.1 Up 10 seconds (healthy) 0.0.0.0:8080->8080/tcp
Если старый образ уже удалён, Docker скачает его заново из реестра (при условии, что тег ещё существует). Для максимальной надёжности запиши полный digest (sha256:...) перед обновлением. Digest неизменяемы. Теги могут быть перезаписаны.
Автоматизация с помощью скрипта перед обновлением
Сохраняй текущее состояние перед каждым обновлением, чтобы откат всегда был в одной команде:
#!/bin/bash
# save-state.sh - Run before every update
COMPOSE_FILE="${1:-docker-compose.yml}"
DATE=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR="./rollback/${DATE}"
mkdir -p "${BACKUP_DIR}"
cp "${COMPOSE_FILE}" "${BACKUP_DIR}/"
docker compose ps --format json > "${BACKUP_DIR}/containers.json"
docker compose images --format json > "${BACKUP_DIR}/images.json"
echo "State saved to ${BACKUP_DIR}"
chmod 700 save-state.sh
Watchtower ещё поддерживается в 2026?
Watchtower был архивирован 17 декабря 2025. Мейнтейнеры больше не используют Docker и прекратили разработку. Последний релиз — v1.7.1. Ещё важнее то, что Docker SDK в Watchtower использует API v1.25, а Docker Engine 29 поднял минимальную версию API до v1.44. Watchtower несовместим с текущими версиями Docker, если не понизить минимум API вручную через DOCKER_MIN_API_VERSION=1.25 в конфигурации daemon. Это костыль, а не решение.
Если ты используешь Watchtower сейчас, планируй миграцию. Для автоматических уведомлений об обновлениях без автоматического перезапуска используй Diun. Для автоматических обновлений без простоя используй docker-rollout за reverse proxy.
Как Diun уведомляет об обновлениях Docker-образов?
Diun (Docker Image Update Notifier) мониторит твои Docker-реестры и отправляет уведомления, когда доступны новые версии образов. Он не обновляет контейнеры. Он сообщает, что обновление существует, чтобы ты мог прочитать changelog и обновиться на своих условиях. Это подход «сначала узнай, потом действуй».
Добавь Diun в существующий compose-файл или создай отдельный:
services:
diun:
image: crazymax/diun:4
command: serve
volumes:
- "diun-data:/data"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
environment:
TZ: "Europe/Berlin"
DIUN_WATCH_WORKERS: "10"
DIUN_WATCH_SCHEDULE: "0 6 * * *"
DIUN_PROVIDERS_DOCKER: "true"
DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT: "true"
DIUN_NOTIF_SLACK_WEBHOOKURL_FILE: "/run/secrets/slack_webhook"
secrets:
- slack_webhook
restart: unless-stopped
secrets:
slack_webhook:
file: ./secrets/slack_webhook.txt
volumes:
diun-data:
URL Slack-вебхука хранится в файле секретов, а не в переменной окружения, потому что Docker secrets скрывает его от вывода docker inspect и списков процессов. Создай файл секретов с ограниченными правами:
mkdir -p secrets
echo "https://hooks.slack.com/services/YOUR/WEBHOOK/URL" > secrets/slack_webhook.txt
chmod 600 secrets/slack_webhook.txt
Основные настройки:
- DIUN_WATCH_SCHEDULE: Cron-выражение.
0 6 * * *проверяет ежедневно в 06:00. Подстрой под своё окно обслуживания. - DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT: При
trueDiun мониторит все запущенные контейнеры. Установиfalseи используй labels для выборочного мониторинга. - Монтирование Docker-сокета: Только для чтения (
:ro), потому что Diun только читает метаданные контейнеров. Он никогда не запускает и не останавливает контейнеры.
Для выборочного мониторинга (рекомендуется для больших стеков) установи WATCHBYDEFAULT в false и добавь labels к контейнерам, которые хочешь мониторить:
services:
app:
image: myapp:2.4.1
labels:
- "diun.enable=true"
- "diun.watch_repo=true"
Запусти Diun и проверь логи:
docker compose up -d diun
docker compose logs diun --tail 20
diun | Thu, 19 Mar 2026 06:00:01 CET INF Starting Diun version=v4.31.0
diun | Thu, 19 Mar 2026 06:00:01 CET INF Configuration loaded from 5 environment variable(s)
diun | Thu, 19 Mar 2026 06:00:02 CET INF Cron triggered
diun | Thu, 19 Mar 2026 06:00:03 CET INF New image found image=docker.io/myapp:2.5.0 provider=docker
Когда Diun находит новый образ, он отправляет сообщение в Slack с именем образа, текущим тегом и новым тегом. Ты сам решаешь, обновляться ли и когда.
Как docker-rollout обеспечивает обновления без простоя?
docker-rollout — это плагин Docker CLI, который выполняет blue-green деплой для Compose-сервисов. Он запускает новый контейнер из обновлённого образа, ждёт прохождения health check, затем удаляет старый контейнер. Трафик никогда не попадает на неработающий контейнер, потому что reverse proxy маршрутизирует только к healthy контейнерам.
Требования:
- Reverse proxy (Traefik, Caddy или nginx-proxy), маршрутизирующий трафик к сервису
- Health check определены в compose-файле
- Нет директивы
container_nameу сервиса (docker-rollout управляет именами контейнеров) - Нет прямого маппинга
portsу сервиса (reverse proxy управляет открытием портов)
Установка docker-rollout
mkdir -p /usr/local/lib/docker/cli-plugins
curl -fsSL https://raw.githubusercontent.com/wowu/docker-rollout/main/docker-rollout \
-o /usr/local/lib/docker/cli-plugins/docker-rollout
chmod +x /usr/local/lib/docker/cli-plugins/docker-rollout
После установки должна отобразиться версия:
docker rollout --version
docker-rollout version v0.13
Пример: Traefik + docker-rollout
Минимальный compose-файл для веб-приложения за Traefik с health check. У приложения нет ports и container_name, потому что docker-rollout должен управлять масштабированием.
services:
traefik:
image: traefik:3.3
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
ports:
- "80:80"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
restart: unless-stopped
app:
image: myapp:2.4.1
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.example.com`)"
- "traefik.http.routers.app.entrypoints=web"
- "traefik.http.services.app.loadbalancer.server.port=8080"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 15s
timeout: 5s
retries: 3
start_period: 30s
restart: unless-stopped
Деплой без простоя
Скачай новый образ и используй docker rollout вместо docker compose up -d:
docker compose pull app
docker rollout app
==> Scaling 'app' to '2' instances
Container myproject-app-2 Creating
Container myproject-app-2 Created
Container myproject-app-2 Starting
Container myproject-app-2 Started
==> Waiting for new containers to be healthy (timeout: 60 seconds)
==> Stopping and removing old containers
Во время этого процесса Traefik обнаруживает новый контейнер через Docker-сокет, направляет трафик на него после перехода в состояние healthy и прекращает маршрутизацию к старому контейнеру перед его удалением. Пользователи не замечают прерывания.
Если новый контейнер не проходит health check, docker-rollout отменяет операцию, и старый контейнер продолжает работать. Ручное вмешательство не требуется.
Что такое blue-green деплой с Docker и Traefik?
Blue-green деплой запускает две копии сервиса (blue и green). Одна обслуживает живой трафик, другая простаивает. Для деплоя обновляешь неактивную копию, проверяешь её работоспособность, затем переключаешь трафик. Это даёт мгновенный откат — достаточно переключить обратно на предыдущую копию.
Это концепция, стоящая за docker-rollout, но ты можешь реализовать её вручную для большего контроля. Минимальный пример с динамической конфигурацией Traefik:
services:
traefik:
image: traefik:3.3
command:
- "--providers.file.directory=/etc/traefik/dynamic"
- "--providers.file.watch=true"
- "--entrypoints.web.address=:80"
ports:
- "80:80"
volumes:
- "./traefik/dynamic:/etc/traefik/dynamic:ro"
restart: unless-stopped
app-blue:
image: myapp:2.4.1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 15s
timeout: 5s
retries: 3
start_period: 30s
app-green:
image: myapp:2.4.1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 15s
timeout: 5s
retries: 3
start_period: 30s
Файл динамической конфигурации Traefik определяет, какая копия получает трафик:
# traefik/dynamic/app.yml
http:
routers:
app:
rule: "Host(`app.example.com`)"
service: app
entryPoints:
- web
services:
app:
loadBalancer:
servers:
- url: "http://app-blue:8080"
Для деплоя: обнови образ app-green, запусти его, дождись перехода в healthy, затем отредактируй app.yml, чтобы он указывал на app-green. Traefik подхватит изменение автоматически, потому что watch=true. Для отката отредактируй файл, чтобы он снова указывал на app-blue.
Этот подход требует больше работы, чем docker-rollout. Используй его, когда нужен явный контроль над переключением, когда хочешь прогнать smoke-тесты на новой версии перед переключением трафика, или когда несколько сервисов должны переключиться одновременно.
Какой метод обновления выбрать?
Выбирай метод, соответствующий допустимому времени простоя и сложности инфраструктуры.
| Метод | Простой | Сложность | Откат | Нужен reverse proxy | Подходит для |
|---|---|---|---|---|---|
docker compose pull + up |
2–10 секунд | Низкая | Ручной (редактирование compose-файла) | Нет | Личные проекты, внутренние инструменты |
| Diun + ручное обновление | То же | Низкая | То же | Нет | Команды, которым нужна видимость перед обновлением |
| docker-rollout | Нет | Средняя | Автоматический (отмена при сбое) | Да | Продакшн-сервисы на одном VPS |
| Blue-green (ручной) | Нет | Высокая | Мгновенный (переключение конфиг-файла) | Да | Многосервисные стеки, регулируемые среды |
Порядок принятия решения:
- 2–10 секунд простоя допустимы? Используй
docker compose pull && docker compose up -d. - Хочешь знать об обновлениях до их применения? Добавь Diun.
- Нужен нулевой простой? Есть reverse proxy? Используй docker-rollout.
- Нужен явный контроль над переключением трафика? Реализуй blue-green вручную.
Что-то пошло не так?
Контейнер запускается, но показывает (unhealthy)
Проверь команду health check. Выполни её вручную внутри контейнера:
docker compose exec app curl -f http://localhost:8080/health
Если не проходит, проблема в приложении, а не в Docker. Смотри логи приложения:
docker compose logs app --tail 50
Старый образ удалён, откат невозможен
Если тег ещё существует в реестре, docker compose pull скачает его. Если закреплял по digest, Docker скачает точный образ независимо от изменений тегов:
image: myapp:2.4.1@sha256:789fed654cba...
docker-rollout зависает во время деплоя
Health check не проходит в отведённое время. Проверь interval и retries health check. Увеличь timeout:
docker rollout -t 120 app
Watchtower перестал работать после обновления Docker
Docker Engine 29 требует минимум API v1.44. Watchtower использует API v1.25. Мигрируй на Diun для уведомлений или docker-rollout для автоматических обновлений без простоя.
Diun не обнаруживает новые образы
Проверь cron-расписание в DIUN_WATCH_SCHEDULE. Запусти сканирование вручную:
docker compose exec diun diun image list
Проверь логи Diun на ошибки аутентификации в реестре:
docker compose logs diun --tail 30
Авторское право 2026 Virtua.Cloud. Все права защищены. Данный контент является оригинальным произведением команды Virtua.Cloud. Воспроизведение, повторная публикация или распространение без письменного разрешения запрещены.
Готовы попробовать?
Разверните свой сервер за секунды. Linux, Windows или FreeBSD.
Смотреть тарифы VPS