SSD Nodes Learn Hosting plans →
Посібники Matt ConnorВід Matt Connor · Оновлено 2026-08-28

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

Що насправді робить кожне значення restart

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

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

Для stack, який має працювати завжди, коли працює сервер, правильним значенням за замовчуванням буде unless-stopped. Вибирайте always лише тоді, коли container не повинен залишатися вимкненим.

Чому restart: on-failure не зберігається після перезавантаження

Багато хто вибирає on-failure, бо це здається обережним варіантом, а після першого перезавантаження виявляє, що всі контейнери зупинені. Причина випливає з визначення цієї політики. on-failure реагує лише на одну подію: завершення процесу контейнера з кодом помилки.

Перезавантаження не є помилкою. Коли хост вимикається, systemd зупиняє docker.service, а daemon навмисно зупиняє кожен контейнер. Контейнер не завершився через помилку, тому політика не має на що реагувати. Після запуску системи daemon перевіряє контейнери, які потрібно відновити, але контейнер із політикою 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, який запускає демон на вимогу, щойно до Docker API звертається будь-який процес. Користувачі бачать, що docker.socket увімкнений, вважають, що демон уже налаштований, і вимикають docker.service для економії пам’яті. Під час завантаження системи до API ніхто не звертається, тому сокет не використовується, демон не запускається, і жоден контейнер не стартує, доки ви не введете першу команду docker. Активація через сокет не замінює увімкнений docker.service.

Коли unit systemd є кращим рішенням

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

Створіть unit systemd, якщо виконується хоча б одна з цих умов. Стеку спочатку потрібно дочекатися монтування, VPN-інтерфейсу або іншого unit. Ви хочете, щоб systemctl stop myapp і systemctl start myapp працювали так само, як і для будь-якого іншого сервісу в системі. Або ви хочете коректно зупиняти стек під час завершення роботи, а не завершувати його разом із демоном. Якщо ви ще не працювали з unit systemd, у матеріалі як створити сервіс і timer 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 позначеним як активний, щоб 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 обробляє контейнер, який аварійно завершився о третій годині ночі.

Unit має інший вигляд, якщо потрібно підтримувати роботу звичайного довготривалого процесу, а не стека. У такому разі під ним немає daemon, і контролювання має виконувати власний Restart= systemd; запуск dsh без headless-режиму під керуванням 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 під час завантаження, зокрема помилку отримання image або відсутній файл .env. Заплановане перезавантаження — це те, за яким ви спостерігаєте, тому unit має повідомляти про ті перезавантаження, за якими ви не стежите: рядок OnFailure=, спрямований на self-hosted сервер ntfy, перетворює стек, який не відновився після перезавантаження, на push-сповіщення, а не на проблему, яку ви виявите через кілька днів.

Що непомітно порушує автоматичний запуск

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

Відносний шлях у volume або в записі env_file визначається відносно каталогу файлу Compose. Це працює з вашої shell і працює з unit, у якому задано WorkingDirectory. З unit без цього параметра шлях визначається неправильно, оскільки робочим каталогом стає /.

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

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

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

І ще одне. Автоматичні оновлення безпеки можуть перезавантажувати сервер у визначений час. Це корисно лише тоді, коли ваш стек запускається самостійно. Налаштування цього на новій машині є частиною початкових завдань, описаних у перших десяти хвилинах на новому VPS.

FAQ

Яка різниця між restart: always і restart: unless-stopped?

Обидві політики перезапускають контейнер, коли він зупиняється самостійно. Вони відрізняються після ручної зупинки контейнера. З always контейнер запускається знову під час наступного запуску Docker daemon, тому перезавантаження скасовує ручну зупинку. З unless-stopped daemon запам’ятовує, що контейнер було навмисно зупинено, і не запускає його. Використовуйте 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 unit, якщо я вже використовую політики перезапуску?

Зазвичай ні. Політики перезапуску достатньо для stack, якому потрібна лише мережа. Так працює більшість stack. Додайте unit, якщо контейнери залежать від ресурсу, який не готовий під час запуску Docker daemon, наприклад зовнішнього диска, зашифрованого тому, NFS-ресурсу або VPN-інтерфейсу. Unit забезпечує порядок запуску через After= і RequiresMountsFor=, чого політика перезапуску забезпечити не може.

Як назавжди зупинити stack, щоб він не запускався знову після наступного перезавантаження?

Для unless-stopped достатньо docker compose stop, оскільки контейнер, зупинений вручну, не запускається після перезапуску daemon. Для always однієї зупинки недостатньо, і контейнер знову запуститься після перезавантаження. Виконайте docker compose down, щоб видалити контейнери, або спочатку змініть політику за допомогою docker update --restart no my-container. Якщо stack керується systemd unit, також виконайте sudo systemctl disable myapp.service. Інакше unit знову його запустить.