SSD Nodes Learn 8GB RAM — $66/год
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-01

Docker Compose: автозапуск контейнеров после перезагрузки

Узнайте, как настроить автозапуск Docker Compose: политика restart, почему on-failure не переживает перезагрузку и когда нужен модуль systemd.

Краткий ответ

Сервисы Docker Compose запускаются при загрузке системы, если одновременно выполняются два условия. Демон Docker должен быть включен как системная служба, а для каждого сервиса в файле должна быть задана политика перезапуска unless-stopped или always. Добавьте restart: unless-stopped для каждого сервиса и один раз выполните docker compose up -d. После перезагрузки контейнеры запустятся автоматически. В обычном случае больше ничего не требуется.

Модуль systemd нужен только тогда, когда важен порядок запуска: например, стек зависит от подключенного диска, интерфейса VPN или сетевого ресурса, который еще не готов в момент запуска демона Docker. Это реальный сценарий, и ему посвящена вторая половина этого руководства. Если вы еще разбираетесь в описаниях сервисов и томах, начните с основ Docker Compose на VPS и вернитесь к этому руководству.

Настройка политики перезапуска в compose.yaml

Политика задается одной строкой для каждой службы. Глобального переключателя нет. Поэтому служба, для которой вы забыли задать политику, останется остановленной после перезагрузки, а остальные службы стека запустятся.

services:
  app:
    image: nginx:1.27
    restart: unless-stopped
    ports:
      - "8080:80"
  db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: change-me
    volumes:
      - dbdata:/var/lib/postgresql/data

volumes:
  dbdata:

Примените изменения, затем прочитайте политику из запущенного контейнера:

docker compose up -d
docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app)

Команда выводит unless-stopped. Если вывод содержит no, файл был изменен, но контейнер не пересоздан.

Это наиболее распространенная причина сбоя. Политика перезапуска хранится в контейнере, а не в YAML-файле. Изменение compose.yaml не влияет на уже существующий контейнер. docker compose restart также не помогает, поскольку останавливает и запускает тот же объект контейнера, не изменяя его конфигурацию. Только docker compose up -d сравнивает файл с запущенными контейнерами, обнаруживает изменение политики и пересоздает контейнеры.

Если контейнер не нужно пересоздавать сейчас, измените политику непосредственно в нем:

docker update --restart unless-stopped my-container

Также измените YAML-файл. docker update изменяет работающий контейнер, а следующая команда docker compose up -d прочитает файл и восстановит старое значение.

Что на самом деле делает каждое значение параметра перезапуска

Docker определяет четыре значения. Разница между ними проявляется только при перезагрузке компьютера или перезапуске демона.

  • no — значение по умолчанию. Контейнер никогда не перезапускается автоматически ни при каких обстоятельствах.
  • always перезапускает контейнер при каждой его остановке. Если вы остановили его вручную, он всё равно запустится при следующем запуске демона Docker. Это часто приводит к неожиданному результату: контейнер, который вы намеренно остановили на прошлой неделе, снова работает после перезагрузки.
  • unless-stopped работает как always, но контейнер, остановленный вручную, остаётся остановленным после перезапуска демона. Это значение подходит для службы, которую вы периодически останавливаете на время обслуживания.
  • on-failure перезапускает контейнер только при завершении с ненулевым кодом выхода. Число попыток можно ограничить, как в restart: on-failure:3.

Для стека, который должен работать всегда, пока работает сервер, используйте unless-stopped. Выбирайте always только в том случае, если контейнер не должен оставаться остановленным.

Почему перезапуск: on-failure не сохраняется после перезагрузки

Многие выбирают on-failure, потому что этот вариант кажется осторожным, а после первой перезагрузки обнаруживают, что все контейнеры остановлены. Причина указана в определении. on-failure реагирует только на одно событие: завершение процесса контейнера с кодом ошибки.

Перезагрузка не является ошибкой. При завершении работы узла systemd останавливает docker.service, а демон намеренно останавливает каждый контейнер. Контейнер не завершился аварийно, поэтому политике не на что реагировать. После запуска системы демон проверяет контейнеры, которые требуется возобновить, но контейнер on-failure, остановленный штатно, к ним не относится. Он остается в состоянии exited.

Это можно проверить напрямую. Установите restart: on-failure для службы, выполните docker compose up -d, перезагрузите систему, затем выполните:

docker compose ps -a

Служба будет указана в состоянии Exited со статусом, например Exited (0) 2 minutes ago. Ошибок нет, и в журнал ничего не записывается. Поэтому эту ситуацию трудно диагностировать. Политика сработала именно так, как указано.

on-failure по-прежнему полезен. Он подходит для контейнера, который выполняет задание и может аварийно завершиться, когда требуется ограниченное число повторных запусков без бесконечного цикла перезапуска. Но для поддержания постоянно работающей службы после перезагрузки это неподходящий инструмент.

Политики перезапуска работают только при запуске службы Docker во время загрузки

Политики перезапуска применяет демон Docker. Если демон не запущен, политики не применяются. Проверьте это:

systemctl is-enabled docker
systemctl is-enabled containerd

Обе команды должны вывести enabled. Пакеты из официального репозитория Docker включают их при установке, поэтому на новом сервере эта проверка обычно проходит. Если одна из команд выводит disabled, исправьте это:

sudo systemctl enable --now docker containerd

Здесь есть важный нюанс. Ubuntu также поставляется с docker.socket, который запускает демон по требованию при первом обращении к API Docker. Вы можете увидеть, что docker.socket включен, решить, что демон уже настроен, и отключить docker.service для экономии памяти. При загрузке системы API никто не вызывает, поэтому сокет не используется, демон не запускается, и контейнеры не запускаются, пока вы не введете первую команду docker. Активация через сокет не заменяет включенный docker.service.

Когда systemd unit — лучший вариант

Политики перезапуска не учитывают порядок запуска относительно остальных компонентов системы. Демон запускается и поднимает контейнеры, как только это становится возможным. Если стек подключает каталог через bind mount из отдельного тома, общей папки NFS (сетевой файловой системы) или зашифрованного диска, контейнеры могут запуститься до появления этого пути. Docker без предупреждения создаст пустой каталог в точке монтирования и запустит контейнер с ним, после чего база данных запустится без данных.

Создайте systemd unit, если выполняется хотя бы одно из следующих условий. Стеку требуется, чтобы сначала был готов mount, интерфейс VPN или другой unit. Вы хотите, чтобы systemctl stop myapp и systemctl start myapp работали так же, как для всех остальных служб на сервере. Или вы хотите корректно остановить стек при завершении работы системы, а не завершать его вместе с демоном. Если systemd units для вас в новинку, в материале создание службы и таймера systemd формат файла описан подробнее.

Написание unit-файла systemd

Разместите стек по фиксированному пути вне домашнего каталога. /srv/myapp — подходящий вариант, поскольку unit, который запускается до входа пользователей в систему, не должен обращаться к /home.

Создайте /etc/systemd/system/myapp.service:

[Unit]
Description=myapp docker compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
RequiresMountsFor=/srv/myapp/data

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/srv/myapp
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target

Включите и запустите его:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
systemctl status myapp.service

Рабочий unit показывает Active: active (exited). При первом просмотре это может выглядеть неправильно. Это корректно: Type=oneshot с параметром RemainAfterExit=yes означает, что unit выполнил свою команду, команда завершилась, а systemd сохраняет для unit состояние active, чтобы ExecStop выполнялся при завершении работы системы.

Каждая строка здесь необходима. Requires=docker.service означает, что unit немедленно завершается с ошибкой, вместо того чтобы выполнять docker compose для неработающего сокета. After= задаёт порядок запуска, поскольку сам по себе Requires= этого не делает. RequiresMountsFor= заставляет systemd подключить mount unit для указанного пути и дождаться его готовности. В этом и заключается основная причина использовать unit вместо политики перезапуска. TimeoutStartSec=0 не позволяет systemd завершить задачу запуска, пока загружается большой образ.

Отдельно о сочетании этих двух механизмов. В документации Docker не рекомендуется сочетать политики перезапуска с менеджером процессов хоста. Это предупреждение относится к менеджеру процессов, который непосредственно контролирует процесс контейнера и перезапускает его, пока daemon пытается делать то же самое. Unit Type=oneshot ничего не контролирует, поэтому сохранять restart: unless-stopped в compose-файле вместе с этим unit безопасно и именно так и следует поступить. systemd управляет порядком запуска при загрузке системы, а daemon обрабатывает контейнер, который аварийно завершился в течение ночи.

Проверьте после реальной перезагрузки

Реальное испытание ничем не заменить. systemctl restart docker не проверяет порядок монтирования, а последовательность docker compose down и docker compose up -d вообще не проверяет загрузку системы.

sudo reboot

Подождите, подключитесь снова и выполните проверки в следующем порядке:

uptime
systemctl is-active docker
docker compose ps

uptime подтверждает, что вы работаете с машиной, которая действительно перезагрузилась. docker compose ps, выполненная из каталога стека, должна показать каждый сервис в состоянии running с временем работы, близким ко времени работы машины. Сервис в состоянии Exited требует проверки.

Если какой-либо компонент не запустился, журнал демона содержит записи за период загрузки:

journalctl -u docker.service -b --no-pager | tail -50

Для стека, управляемого unit-файлом, journalctl -u myapp.service -b --no-pager показывает точный вывод docker compose при загрузке, включая неудачную загрузку образа или отсутствующий файл .env.

Что может незаметно нарушить автоматический запуск

Контейнеры, созданные с помощью docker compose run, не получают политику перезапуска из файла. Compose рассматривает их как разовые контейнеры. Если служба, похоже, игнорирует свою политику, проверьте, была ли она запущена с помощью run, а не up.

Относительный путь в томе или в записи env_file разрешается относительно каталога файла compose. Это работает из вашей оболочки и из модуля, который задаёт WorkingDirectory. В модуле без этого параметра путь не разрешается, потому что рабочим каталогом становится /.

Rootless Docker — отдельный случай. Демон работает как пользовательская служба, а пользовательская служба останавливается после завершения последнего сеанса этого пользователя. Включите её для пользователя и разрешите ей продолжать работу без вошедшего в систему пользователя:

systemctl --user enable docker
sudo loginctl enable-linger $USER

Без enable-linger rootless-демон завершается при выходе из системы вместе с контейнерами. Это выглядит точно так же, как неисправная политика перезапуска.

Есть ещё один момент. Автоматические обновления безопасности могут перезагрузить сервер в заданный час. Это полезно только в том случае, если ваш стек запускается после перезагрузки автоматически. Настройка этого на новом компьютере относится к остальным задачам первого часа: первые десять минут на новом VPS.

FAQ

В чем разница между restart: always и restart: unless-stopped?

Обе политики перезапускают контейнер, если он остановился самостоятельно. Разница проявляется после ручной остановки контейнера. При always контейнер запускается снова при следующем запуске демона Docker, поэтому перезагрузка отменяет ручную остановку. При unless-stopped демон запоминает, что контейнер был остановлен намеренно, и не запускает его. Используйте unless-stopped, если только Вам не нужен контейнер, который не должен запускаться снова.

Я добавил restart: unless-stopped, но контейнер все равно не запускается после перезагрузки. Почему?

Политика хранится в контейнере, а не в файле. Редактирование YAML не изменяет уже существующий контейнер. Выполните docker compose up -d, чтобы Compose пересоздал контейнер, затем проверьте результат с помощью docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app). Если команда выводит no, контейнер был создан до изменения файла. Другой распространенной причиной является то, что docker.service не включен. Это можно проверить с помощью systemctl is-enabled docker.

Нужен ли мне юнит systemd, если я уже использую политики перезапуска?

Обычно нет. Политики перезапуска достаточно для стека, которому нужна только сеть. Так устроено большинство стеков. Добавьте юнит, если контейнеры зависят от компонента, который не готов к моменту запуска демона Docker, например от внешнего диска, зашифрованного тома, общей папки NFS или интерфейса VPN. Юнит задает порядок запуска с помощью After= и RequiresMountsFor=, чего нельзя выразить политикой перезапуска.

Как навсегда остановить стек, чтобы он не запустился снова при следующей перезагрузке?

При unless-stopped достаточно выполнить docker compose stop, поскольку контейнер, остановленный вручную, не запускается снова при перезапуске демона. При always одной остановки недостаточно, и после перезагрузки контейнер запустится снова. Выполните docker compose down, чтобы удалить контейнеры, или сначала измените политику с помощью docker update --restart no my-container. Если стеком управляет юнит systemd, также выполните sudo systemctl disable myapp.service. Иначе юнит запустит стек снова.