docker compose pull && docker compose up -d быстро обновляет стек, но не создаёт точку отката. Проблемы начинаются, когда новый образ меняет схему БД, прежний тег уже указывает на другой артефакт, а формальный healthy ещё не означает, что сайт обслуживает запросы. Поэтому production-обновление лучше проводить как небольшой релиз: с зафиксированными образами, проверенной копией и записанным путём назад.
Ниже — порядок для Docker Compose на одном Linux-сервере. Команды сверены с Compose 5.4.0 и Bash; в Compose 2.x отдельные флаги могут отсутствовать. Имена каталогов, проекта и адрес проверки замените своими.
Важно: у Docker Compose нет универсальной команды rollback. Возврат выполняется запуском предыдущего release bundle. Если обновление необратимо изменило базу, одного старого образа недостаточно — понадобится восстановление данных или документированная обратная миграция.
Проверено: 2026-08-09.
Что подготовить один раз
Храните каждый релиз отдельным комплектом, а не редактируйте единственный compose.yaml на сервере:
/srv/myapp/releases/2026-08-01/
├── compose.yaml
├── compose.production.yaml
├── compose.images.lock.yaml
└── .env
/srv/myapp/releases/2026-08-09/
├── compose.yaml
├── compose.production.yaml
├── compose.images.lock.yaml
└── .env
В Git можно хранить Compose-файлы и инструкцию релиза. .env, файлы из secrets: и ключи шифрования туда не кладут. Если приложение умеет читать секрет из файла, передавайте его через Compose secrets: и поддерживаемую приложением переменную *_FILE, а не через environment:.
В compose.images.lock.yaml нужны не только теги, а точные ссылки формата repository/name:tag@sha256:.... Тег удобен человеку, но изменяем: издатель может передвинуть его на другой образ. Digest неизменяем и позволяет получить именно тот артефакт, который проходил проверку.
Lock для будущего релиза можно сформировать так:
docker compose \
--env-file /srv/myapp/releases/2026-08-09/.env \
-p myapp \
-f /srv/myapp/releases/2026-08-09/compose.yaml \
-f /srv/myapp/releases/2026-08-09/compose.production.yaml \
config --lock-image-digests \
-o /srv/myapp/releases/2026-08-09/compose.images.lock.yaml
Сразу добавьте полученный файл в release bundle и проверьте его. Не пытайтесь впервые создать lock для старой версии во время аварии: к этому моменту изменяемый тег в registry уже может указывать на новый образ.
Этот порядок предполагает опубликованный image: у каждого production-сервиса. Build-only сервисы заранее собирают, публикуют и закрепляют digest; сборка релиза на рабочем сервере здесь не рассматривается.
Всегда сохраняйте одно имя проекта (-p myapp): другое значение создаст соседний стек. Данные держите в named volumes или bind mounts, а БД и Redis — без публичных ports:. Локальный web-сервис можно привязать к 127.0.0.1:8080:8080. Не монтируйте /var/run/docker.sock: доступ к daemon практически равен root-доступу, и :ro это не исправляет.
1. Сверьте будущую версию с документацией проекта
До команд на сервере прочитайте release notes именно перехода со своей версии на новую. Найдите ответы на четыре вопроса:
- меняется ли формат конфигурации;
- есть ли миграции базы и обратимы ли они;
- можно ли старому приложению работать с новой схемой;
- меняются ли права на каталогах, UID/GID контейнера или формат persistent-данных.
Образ берите из официального registry; при наличии проверьте provenance и известные уязвимости. Если документация не обещает совместимость миграции, не запускайте поверх новой схемы старое приложение.
2. Зафиксируйте исходное состояние
Чтобы не повторять длинные параметры и случайно не сменить набор Compose-файлов, задайте две функции в текущем сеансе Bash:
CURRENT=/srv/myapp/releases/2026-08-01
NEXT=/srv/myapp/releases/2026-08-09
dc_current() {
docker compose --env-file "$CURRENT/.env" -p myapp \
-f "$CURRENT/compose.yaml" \
-f "$CURRENT/compose.production.yaml" \
-f "$CURRENT/compose.images.lock.yaml" "$@"
}
dc_next() {
docker compose --env-file "$NEXT/.env" -p myapp \
-f "$NEXT/compose.yaml" \
-f "$NEXT/compose.production.yaml" \
-f "$NEXT/compose.images.lock.yaml" "$@"
}
Сначала проверьте обе итоговые модели. config -q разбирает и объединяет файлы, подставляет переменные, но при успехе не печатает конфигурацию с возможными секретами:
docker compose version
dc_current config -q
dc_next config -q
Сверьте по --help, что установленная версия поддерживает --lock-image-digests, глобальный --dry-run, --wait и --wait-timeout; на старых ветках часть этих флагов отсутствует.
Затем сохраните состояние работающего стека в закрытый служебный каталог:
audit_dir="$NEXT/preflight"
install -d -m 0700 "$audit_dir"
dc_current ps --all > "$audit_dir/containers.before.txt"
dc_current images --format json > "$audit_dir/images.before.json"
dc_current ps -q \
| xargs -r docker inspect --format '{{.Name}} {{.Image}}' \
> "$audit_dir/container-image-ids.before.txt"
dc_current images -q \
| sort -u \
| xargs -r docker image inspect \
--format '{{.Id}} {{json .RepoDigests}}' \
> "$audit_dir/image-digests.before.txt"
Это журнал проверки, а не замена lock-файлу: rollback должен ссылаться на доступный immutable digest. Не удаляйте старые образы до окончания окна наблюдения.
Проверьте свободное место и состав volumes. Не меняйте project name и не добавляйте --remove-orphans, пока не разобрались с каждым найденным контейнером:
df -h
docker system df
dc_current config --volumes
dc_current ps --all
3. Сделайте копию, которую уже пробовали восстанавливать
Для стека с данными нужны как минимум:
- согласованный дамп БД или документированный физический backup;
- named volumes и bind mounts с пользовательскими файлами;
- Compose-файлы, image lock и сведения о версии;
- ключи шифрования и secrets в отдельном защищённом хранилище;
- контрольные суммы, срок хранения и копия вне этого сервера.
Не архивируйте tar-ом каталог работающей PostgreSQL, MariaDB или MySQL и не называйте результат согласованным бэкапом. Для PostgreSQL используйте pg_dump, PITR или штатный физический backup. Для MariaDB/MySQL — mariadb-dump/mysqldump с подходящим режимом согласованности либо остановите запись перед файловым снимком. --single-transaction помогает только для транзакционных таблиц и не разрешает менять DDL во время дампа.
Наличие файла ещё не означает, что восстановление работает. Поднимите копию в отдельном Compose-проекте с отдельными volumes, восстановите данные, выполните вход, чтение и тестовую запись. Почту и внешние интеграции направьте в тестовый транспорт.
Подробная процедура для WordPress уже есть в DevConnect: резервная копия Docker Compose и пробное восстановление. Здесь она не дублируется: перед обновлением нужен свежий результат той процедуры и записанное время восстановления.
4. Сначала загрузите образы, не трогая контейнеры
Убедитесь, что прежние образы ещё доступны, затем загрузите будущие:
dc_current pull
dc_next pull
docker compose pull только загружает образы. Работающие контейнеры он не запускает и не пересоздаёт. Не используйте --ignore-pull-failures: частично загруженный комплект следует считать неготовым релизом.
Посмотрите план Compose без изменения стека:
docker compose --dry-run \
--env-file "$NEXT/.env" -p myapp \
-f "$NEXT/compose.yaml" \
-f "$NEXT/compose.production.yaml" \
-f "$NEXT/compose.images.lock.yaml" \
up -d --pull never --no-build --wait --wait-timeout 120
Dry-run показывает план операций, но не заменяет backup и smoke-тест.
5. Миграцию базы выполняйте отдельным шагом
Если новая версия не меняет данные, переходите к запуску. Если меняет — не прячьте миграцию в непредсказуемом автоматическом обновлении всех контейнеров.
Для документированной идемпотентной миграции обычно используют одноразовую команду приложения:
dc_next run --rm --no-deps --pull never \
app COMMAND_FROM_THE_APPLICATION_DOCUMENTATION
COMMAND_FROM_THE_APPLICATION_DOCUMENTATION — метка, а не команда для копирования. Нужные зависимости текущего релиза уже должны работать. Подставьте только официальный вариант и запускайте его одним процессом. В Compose 5.3+ есть pre_start, но он повторяется при пересоздании: применять его стоит только для идемпотентной миграции после проверки версии Compose.
Перед необратимой миграцией включите maintenance/read-only, остановите writers и заранее выберите один из путей:
- expand/contract: сначала добавляются совместимые поля, старый код продолжает работать, удаление старой схемы выполняется отдельным будущим релизом;
- обратная миграция, если её официально поддерживает проект;
- восстановление БД и связанных файлов до состояния перед релизом.
Последний вариант теряет записи, созданные после копии. Допустимый объём потерь — RPO — нужно принять до обновления, а не во время аварии.
6. Примените релиз без скрытого pull и build
Образы уже загружены и проверены по lock-файлу, поэтому активация не должна неожиданно тянуть другой tag или собирать код на сервере:
dc_next up -d \
--pull never \
--no-build \
--wait \
--wait-timeout 120
Если конфигурация или image изменились, docker compose up пересоздаст нужные контейнеры и сохранит mounted volumes. --force-recreate для обычного digest-based обновления не нужен. docker compose restart тоже не подходит: он не применяет новый образ, environment и изменения Compose-файла.
Не добавляйте в штатное обновление:
docker compose down без прямого требования официальной инструкции приложения — при обычной замене образа он увеличит простой;
docker compose down -v — этот вариант удалит named и подключённые анонимные volumes;
--renew-anon-volumes — создаст новые анонимные volumes;
--remove-orphans — может удалить нужный контейнер при неполном наборе -f, другом profile или переименовании сервиса;
docker volume prune и docker system prune --volumes;
chmod 777, privileged: true и рекурсивный chown без проверки требований образа.
7. Не путайте running, healthy и ready
После up --wait проверьте все контейнеры и свежие журналы:
dc_next ps --all
dc_next ps --format json
dc_next logs --since 10m --tail 200 --timestamps
Docker показывает running — основной процесс запущен — и, при наличии healthcheck, healthy. Ready не является отдельным статусом Compose: это прикладная готовность обслуживать запросы и работать с зависимостями.
up --wait ждёт running или healthy. Без содержательного healthcheck запуск ещё ничего не говорит о готовности. Проверьте сервис снаружи Docker:
curl --fail --silent --show-error --max-time 5 \
https://example.com/ready >/dev/null
Если /ready нет, проверьте публичную страницу и безопасный сценарий через API: авторизацию, чтение и обращение к БД. Для очереди или cron нужен свой сигнал. Наблюдайте логи, HTTP-ошибки и метрики весь выбранный период.
8. Откат без несовместимой миграции
Если новая версия не меняла схему либо миграция обратно совместима, сначала найдите сервисы, которых не было в прежнем release bundle:
comm -13 \
<(dc_current config --services | sort) \
<(dc_next config --services | sort)
Адресно остановите все найденные writer-, worker-, cron- и конфликтующие по портам сервисы: dc_next stop SERVICE.... Не подставляйте список автоматически: сначала проверьте назначение каждого сервиса. Затем запустите предыдущий bundle с тем же именем проекта:
dc_current up -d \
--pull never \
--no-build \
--wait \
--wait-timeout 120
curl --fail --silent --show-error --max-time 5 \
https://example.com/ready >/dev/null
Затем выполните ps --all, проверьте логи, основной сценарий и убедитесь, что NEXT-only writers/cron остановлены. Stateless orphan-сервисы разбирайте отдельно после восстановления.
Если команда с --pull never сообщает, что старого образа нет, это не повод заменить digest старым тегом. Верните точный образ из доверенного registry или заранее сохранённого артефакта.
9. Откат после несовместимой миграции
Возврат контейнера не возвращает схему и данные. Старый код поверх новой несовместимой схемы может не запуститься или, хуже, продолжить работу с повреждением данных.
Безопасный порядок зависит от приложения, но границы одинаковы:
- остановить новые записи или включить maintenance/read-only;
- сохранить диагностические логи, не печатая secrets;
- остановить все сервисы, использующие восстанавливаемую БД или volume;
- выполнить штатный restore из одной точки времени; безопасная альтернатива — восстановить в изолированные DB/volumes и сделать контролируемое переключение;
- запустить предыдущий release bundle по точным digest;
- пройти health- и функциональную проверку;
- явно зафиксировать потерянный интервал данных, если восстановление вернуло состояние к backup.
Не восстанавливайте данные поверх storage, подключённого к работающим контейнерам, и не запускайте старый образ «на пробу» поверх неизвестной схемы. Непроверенный restore — не план отката.
Частые сбои и что проверять
pull завершился ошибкой
Работающий стек не изменился. Проверьте registry, digest, место на диске и архитектуру образа. Не переходите к up с неполным набором.
up --wait истёк по тайм-ауту
up не является транзакцией: часть контейнеров уже могла обновиться. Сравните состояния и image ID всех сервисов, затем исправляйте причину или возвращайте полный предыдущий bundle. Смотрите ps --all, healthcheck и логи; не увеличивайте тайм-аут вслепую.
Миграция завершилась ошибкой
Не повторяйте её вслепую: схема могла измениться частично. Повтор допустим при документированной идемпотентности; иначе нужны диагностика, forward-fix или restore.
Короткий чек-лист перед Enter
- release notes и совместимость миграций прочитаны;
- оба Compose-комплекта проходят
config -q;
- current и next используют одинаковые
-p, -f, profiles и env-файл, а образы закреплены digest;
- нет публичной БД, Docker socket и secrets в Git или логах;
- сделан application-aware backup БД и файлов;
- restore проверен в отдельном проекте;
- миграция — отдельный шаг, после
up --wait есть внешний smoke-тест;
- предыдущий bundle хранится до конца rollback window;
- для несовместимой миграции записан план восстановления данных.
Такое обновление занимает больше времени при подготовке, зато авария перестаёт быть импровизацией: известно, какой код работал, где лежат данные, какая команда возвращает прежний релиз и в какой момент требуется восстановление БД.
Официальные источники