SSD Nodes Learn 8GB RAM — $66/рік
Посібники Matt ConnorВід Matt Connor · Оновлено 2026-08-01

Docker Compose: автозапуск після перезавантаження

Налаштуйте автозапуск Docker Compose після reboot: політики restart, чому on-failure не переживає перезавантаження та коли потрібен systemd unit.

Коротка відповідь

Сервіси 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 прочитає файл і поверне попереднє значення.

Що фактично робить кожне значення restart

Docker визначає чотири значення. Різниця між ними проявляється лише після перезавантаження машини або перезапуску daemon.

  • no — значення за замовчуванням. Контейнер ніколи не перезапускається автоматично за жодних обставин.
  • always перезапускає контейнер щоразу після його зупинки. Якщо ви зупинили його вручну, він усе одно запуститься під час наступного запуску Docker daemon. Це часто дивує: контейнер, який ви навмисно зупинили минулого тижня, знову працює після перезавантаження.
  • unless-stopped працює як always, але контейнер, зупинений вручну, залишається зупиненим після перезапуску daemon. Це потрібне значення для служби, яку періодично зупиняють на час технічного обслуговування.
  • on-failure перезапускає контейнер лише тоді, коли він завершує роботу з ненульовим кодом завершення. Кількість спроб можна обмежити, як у restart: on-failure:3.

Для stack, який має працювати щоразу, коли працює server, правильним значенням за замовчуванням буде 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 докладніше описано формат файлу.

Написання модуля systemd

Розмістіть стек у фіксованому каталозі поза домашнім каталогом. /srv/myapp — вдалий вибір, оскільки модулю, який запускається до входу будь-якого користувача в систему, не потрібно читати /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

Справний модуль має стан Active: active (exited). Спочатку це може виглядати неправильно. Але це очікувана поведінка: Type=oneshot із RemainAfterExit=yes означає, що модуль виконав свою команду, команда завершилася, а systemd залишає модуль позначеним як активний, щоб ExecStop виконався під час завершення роботи системи.

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

Важливо правильно поєднати ці два механізми. Документація Docker не радить одночасно використовувати політики перезапуску та менеджер процесів хоста. Це попередження стосується менеджера процесів, який безпосередньо контролює процес контейнера й перезапускає його, поки демон намагається робити те саме. Модуль Type=oneshot нічого не контролює, тому зберігати restart: unless-stopped у compose-файлі разом із цим модулем можна і потрібно. systemd керує порядком запуску під час завантаження системи, а демон обробляє ситуацію, коли контейнер аварійно завершується о третій годині ночі.

Перевірка справжнім перезавантаженням

Справжній тест не має заміни. 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-файлу. Це працює з оболонки та з unit, у якому задано WorkingDirectory. У unit без цього параметра шлях не визначається правильно, оскільки робочим каталогом стає /.

Rootless Docker — окремий випадок. Демон працює як користувацька служба, а користувацька служба зупиняється після завершення останнього сеансу цього користувача. Увімкніть її для користувача та дозвольте їй працювати без активного входу користувача:

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

Без enable-linger rootless daemon завершує роботу після виходу з системи, а контейнери зупиняються разом із ним. Це виглядає так само, як несправна політика перезапуску.

І ще один момент. Автоматичні оновлення безпеки можуть перезавантажити сервер у визначену годину. Це корисно лише тоді, коли ваш стек запускається самостійно. Налаштування цього на новій машині належить до решти робіт першої години, описаних у розділі перші десять хвилин на новому 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, інакше модуль знову його запустить.