Запуск Docker Compose как службы Linux с помощью systemd

Запуск Docker Compose при загрузке, управляемый через systemd.

Содержимое страницы

Docker Compose на Linux-сервере должен запускаться при загрузке, корректно останавливаться при выключении и переживать перезагрузки без ручного вмешательства.

Docker Compose — это не Kubernetes, и для рабочих нагрузок, на которые ориентировано данное руководство, это вполне подходит. Для многих реальных систем проект Compose на одном Linux-хосте является оптимальным количеством инфраструктуры: он прост, понятен, легко резервируется и вполне достаточен для внутренних инструментов, побочных проектов, self-hosted сервисов, staging-сред, небольших продакшен-приложений и инфраструктуры разработчиков.

docker compose config ont the table with laptop

Обычно не хватает механизма управления сервисами. Ручного запуска недостаточно:

docker compose up -d

Одна команда запускает стек, но она не документирует, как стек должен запускаться при загрузке, останавливаться при выключении, перезагружаться после изменений, вести логи, восстанавливаться после сбоев или безопасно обновляться. Здесь на помощь приходит systemd.

В этом руководстве рассматривается запуск проекта Docker Compose в качестве Linux-сервиса с помощью systemd — юнит-файлы, порядок загрузки, обновления, логи и резервное копирование. Разделение ответственности намеренное: Docker запускает контейнеры, Compose определяет стек, а systemd запускает и останавливает проект на хосте. Это часть раздела Инструменты разработчика — руководство по рабочим процессам.

Когда имеет смысл запускать Docker Compose как сервис

Запуск Compose под systemd имеет смысл, когда у вас есть:

  • Единый Linux-сервер
  • Небольшое self-hosted приложение
  • Стек обратного прокси
  • Стек мониторинга
  • Локальная платформа разработки
  • Внутренний инструмент
  • Staging-окружение
  • Простой продакшен-сервис с известными ограничениями

Примеры:

  • Nginx Proxy Manager
  • Traefik
  • Gitea
  • Grafana и Prometheus
  • PostgreSQL плюс небольшое веб-приложение
  • Uptime Kuma
  • Вспомогательные сервисы Home Assistant
  • Приватный реестр
  • Внутренний API плюс воркер плюс Redis

Compose хорошо подходит, когда операционная модель остается понятной одному человеку, просматривающему одну директорию.

Когда Docker Compose недостаточно

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

  • Планирование на нескольких узлах
  • Автоматическое перепланирование между хостами
  • Сервисная discovery на уровне кластера
  • Горизонтальное автомасштабирование
  • Последовательные развертывания на многих машинах
  • Тонкозернистая идентификация рабочих нагрузок
  • Сложная сетевая политика
  • Крупные платформенные операции для нескольких команд

На этом этапе Kubernetes, Nomad, Swarm или управляемая платформа могут подходить лучше.

Мое практическое правило: избегать использования Kubernetes просто чтобы не изучать systemd, и избегать использования Compose, когда рабочая нагрузка явно требует оркестрации на нескольких хостах.

Базовая архитектура

Чистая настройка разделяет файлы проекта, systemd-юнит и постоянные данные на хосте. Проект Compose находится в /opt/myapp/ с файлами compose.yaml, .env, data/, backups/ и опциональными скриптами, такими как scripts/update.sh. Файл юнита systemd находится в /etc/systemd/system/myapp.service.

flowchart TB subgraph host["Linux host"] systemd["systemd unit\n/etc/systemd/system/myapp.service"] compose["Docker Compose\n/opt/myapp/compose.yaml"] docker["Docker Engine"] fs["Persistent data\n/opt/myapp/data/"] end systemd -->|"ExecStart: docker compose up -d"| compose compose --> docker docker --> fs

У каждого слоя есть четкая задача: Docker запускает контейнеры, Compose определяет стек приложения, systemd запускает и останавливает проект Compose при загрузке и выключении, файловая система хоста хранит постоянные данные, резервные копии остаются явными, а обновления проходят через скриптованные, проверяемые шаги. Эта структура намеренно скучна, потому что скучная инфраструктура легче в ремонте, когда что-то ломается в 2 часа ночи.

Подготовка директории проекта Compose

Создайте директорию в /opt:

sudo mkdir -p /opt/myapp
sudo chown -R "$USER":"$USER" /opt/myapp
cd /opt/myapp

Создайте файл Compose:

nano compose.yaml

Пример:

services:
  web:
    image: nginx:stable
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
    healthcheck:
      test: ["CMD-SHELL", "nginx -t || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

volumes: {}

Создайте директорию контента:

mkdir -p html
echo "Hello from Docker Compose" > html/index.html

Сначала протестируйте вручную:

docker compose up -d
docker compose ps
docker compose logs --tail=50

Затем остановите его перед передачей управления жизненным циклом systemd:

docker compose down

Не создавайте сервис systemd, пока проект Compose не заработает вручную. Во время тестов держите под рукой Шпаргалку по Docker Compose для ps, logs, pull и структуры проекта.

Используйте современную команду docker compose

Docker Engine и плагин Compose должны быть установлены перед написанием файла юнита. На Ubuntu руководство Установка Docker на Ubuntu описывает APT, Snap, режим без прав root и безопасность после установки, чтобы у вас получилась рабочая команда docker compose.

Используйте это:

docker compose version

А не это:

docker-compose version

Старый бинарный файл docker-compose все еще существует на многих машинах, но современный Docker использует Compose как плагин CLI Docker.

В файлах сервисов и скриптах предпочитайте:

/usr/bin/docker compose

Путь к Docker можно найти с помощью:

command -v docker

Обычно это:

/usr/bin/docker

Создание systemd-сервиса для Docker Compose

Если файлы юнитов вам новы, руководство Запуск любого исполняемого файла как сервиса в Linux объясняет Type, ExecStart, systemctl и общий рабочий процесс systemd. Этот раздел применяет эти паттерны специально к стеку Compose.

Создайте файл сервиса:

sudo nano /etc/systemd/system/myapp.service

Используйте этот юнит:

[Unit]
Description=MyApp Docker Compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

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

[Install]
WantedBy=multi-user.target

Перезагрузите systemd:

sudo systemctl daemon-reload

Запустите сервис:

sudo systemctl start myapp.service

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

sudo systemctl enable myapp.service

Проверьте статус:

systemctl status myapp.service

Проверьте контейнеры:

cd /opt/myapp
docker compose ps

Почему Type=oneshot и RemainAfterExit=yes?

Это та часть, которую многие руководства понимают неверно.

docker compose up -d запускает контейнеры в отсоединенном режиме и завершается, поэтому нет долгоживущего фонового процесса Compose, за которым systemd мог бы наблюдать. Юнит systemd не должен притворяться, что docker compose up -d — это долгоживущий демон.

Используйте:

Type=oneshot
RemainAfterExit=yes

Это говорит systemd:

  • Выполнить команду старта.
  • Считать юнит активным после успешного завершения команды.
  • Выполнить ExecStop при остановке сервиса.

Это соответствует фактическому поведению отсоединенного Compose, поэтому Type=oneshot с RemainAfterExit=yes является правильным значением по умолчанию для большинства стеков.

Почему не Type=simple?

С Type=simple systemd ожидает, что процесс ExecStart будет продолжать работу, но docker compose up -d завершается после запуска контейнеров. Это может заставить systemd думать, что сервис завершился, затем вызвать логику остановки или пометить юнит неактивным в зависимости от конфигурации.

Если вы хотите Type=simple, вы обычно запускали бы Compose в переднем плане:

ExecStart=/usr/bin/docker compose up

Это может работать, но я обычно не предпочитаю это для стеков Compose на серверах. Отсоединенные контейнеры плюс явный ExecStop проще в эксплуатации.

Более продакшен-ориентированный юнит

Для реального сервера я предпочитаю немного более строгий юнит:

[Unit]
Description=MyApp Docker Compose stack
Documentation=https://example.com/docs/myapp
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/myapp
EnvironmentFile=-/opt/myapp/.env.systemd
ExecStartPre=/usr/bin/docker compose config --quiet
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecReload=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
TimeoutStopSec=120

[Install]
WantedBy=multi-user.target

Важные детали:

  • WorkingDirectory указывает на проект Compose.
  • ExecStartPre валидирует конфигурацию Compose.
  • ExecReload пересоздает измененные сервисы.
  • ExecStop останавливает и удаляет контейнеры проекта Compose и сетью по умолчанию.
  • EnvironmentFile=-... означает, что файл опционален.

Создайте опциональный файл окружения systemd:

nano /opt/myapp/.env.systemd

Пример:

COMPOSE_PROJECT_NAME=myapp

Затем перезагрузите systemd:

sudo systemctl daemon-reload
sudo systemctl restart myapp.service

Compose .env против systemd EnvironmentFile

Compose и systemd имеют собственные механизмы окружения, и их смешивание вызывает запутанные ошибки “переменная не установлена” при загрузке.

Compose автоматически читает файл .env в директории проекта для подстановки переменных в файле Compose.

Пример .env:

APP_TAG=1.2.3
WEB_PORT=8080

Пример compose.yaml:

services:
  web:
    image: nginx:${APP_TAG}
    ports:
      - "${WEB_PORT}:80"

EnvironmentFile в systemd устанавливает переменные окружения для самой команды docker compose.

Пример:

EnvironmentFile=-/opt/myapp/.env.systemd

Для многих проектов вам нужен только .env Compose.

Используйте файл окружения systemd, когда вы хотите определить такие вещи, как:

COMPOSE_PROJECT_NAME=myapp
COMPOSE_FILE=compose.yaml
DOCKER_HOST=unix:///var/run/docker.sock

Не используйте ни один из файлов как хранилище секретов. Если секреты важны, используйте Docker secrets, внешний менеджер секретов, зашифрованные файлы или хотя бы строгие права доступа.

Установите строгие права доступа:

chmod 600 /opt/myapp/.env
chmod 600 /opt/myapp/.env.systemd

Политики перезапуска: Docker против systemd

Есть два уровня перезапуска — политика перезапуска контейнеров в Compose и политика перезапуска сервиса в systemd — и их не следует слепо смешивать.

Для долгоживущих контейнеров устанавливайте политики перезапуска в Compose:

services:
  web:
    image: nginx:stable
    restart: unless-stopped

Распространенные значения перезапуска:

Политика Значение
no Не перезапускать автоматически
always Перезапускать после выхода и перезапуска демона
on-failure Перезапускать только после сбоя
unless-stopped Перезапускать, если не остановлено вручную

Для большинства постоянных сервисов я предпочитаю:

restart: unless-stopped

Это предсказуемо и уважает намеренные ручные остановки.

Сам юнит systemd обычно не должен перезапускаться многократно, потому что docker compose up -d — это не рабочая нагрузка. Контейнеры — это рабочая нагрузка.

Поэтому избегайте этого, если у вас нет конкретной причины:

Restart=always

В большинстве юнитов Compose-as-service позвольте Docker обрабатывать перезапуски контейнеров.

Health Checks

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

Добавляйте health checks там, где они полезны:

services:
  app:
    image: example/app:latest
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://localhost:8080/health || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s

Проверьте здоровье:

docker compose ps

Осмотрите контейнер:

docker inspect container-name

Health checks особенно полезны для:

  • Веб-приложений
  • Обратных прокси
  • Баз данных
  • Очерей
  • Внутренних API
  • Воркеров с эндпоинтом здоровья

Они менее полезны, когда они проверяют только наличие процесса, потому что процесс, который жив, но завис, все еще выглядит здоровым. Плохой health check — это просто еще одна ложь в YAML.

Порядок запуска и depends_on

Compose может определять зависимости:

services:
  app:
    image: example/app:latest
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

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

Логи: journalctl и docker compose logs

Два вида логов покрывают большинство задач отладки: systemd захватывает жизненный цикл самого юнита, в то время как Compose захватывает вывод приложения из работающих контейнеров.

Логи сервиса systemd:

journalctl -u myapp.service -n 100 --no-pager

Следить за логами systemd:

journalctl -u myapp.service -f

Логи сервиса Compose:

cd /opt/myapp
docker compose logs --tail=100
docker compose logs -f
docker compose logs -f web

Для большинства задач отладки приложений docker compose logs более полезен; для отладки жизненного цикла — сбоев запуска, крашей юнита, ошибок прав доступа — journalctl более полезен. Если systemctl start myapp не удалось, сначала проверьте journalctl. Если стек запустился, но приложение сломано, проверьте docker compose logs.

Ротация логов

Логи Docker могут расти бесконечно, если вы не настроите их.

Для небольших серверов настройте ротацию логов Docker в /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "5"
  }
}

Перезапустите Docker:

sudo systemctl restart docker

Затем перезапустите стек Compose:

sudo systemctl restart myapp.service

Это применяется к вновь созданным контейнерам. Пересоздайте контейнеры при необходимости:

cd /opt/myapp
docker compose up -d --force-recreate

Ротация логов не гламурна, но это один из самых простых способов предотвратить outage из-за полного диска на небольшом сервере.

Обновление сервиса Compose

Простой ручной поток обновлений:

cd /opt/myapp
docker compose pull
docker compose up -d --remove-orphans
docker image prune -f

Если управляется systemd, вы можете использовать:

sudo systemctl reload myapp.service

Если ваш юнит имеет:

ExecReload=/usr/bin/docker compose up -d --remove-orphans

Но обратите внимание: ExecReload не тянет образы, если вы не включили этот шаг.

Для явных обновлений создайте скрипт.

mkdir -p /opt/myapp/scripts
nano /opt/myapp/scripts/update.sh

Скрипт:

#!/usr/bin/env bash
set -euo pipefail

cd /opt/myapp

docker compose config --quiet
docker compose pull
docker compose up -d --remove-orphans
docker image prune -f
docker compose ps

Сделайте его исполняемым:

chmod +x /opt/myapp/scripts/update.sh

Запустите его:

/opt/myapp/scripts/update.sh

Затем юнит сервиса может оставаться сфокусированным на жизненном цикле, в то время как скрипт обновления обрабатывает развертывание.

Более безопасный скрипт обновления с хуком резервного копирования

Для сервисов с состоянием обновляйте только после резервного копирования.

#!/usr/bin/env bash
set -euo pipefail

APP_DIR="/opt/myapp"
BACKUP_DIR="/opt/myapp/backups"

cd "$APP_DIR"

mkdir -p "$BACKUP_DIR"

echo "Validating compose file"
docker compose config --quiet

echo "Running backup hook"
if [ -x "$APP_DIR/scripts/backup.sh" ]; then
  "$APP_DIR/scripts/backup.sh"
else
  echo "No backup hook found"
fi

echo "Pulling images"
docker compose pull

echo "Recreating services"
docker compose up -d --remove-orphans

echo "Pruning unused images"
docker image prune -f

echo "Current status"
docker compose ps

Это все еще просто, но теперь это кодирует операционную привычку: резервное копирование перед изменением.

Остановка сервиса

Остановите стек:

sudo systemctl stop myapp.service

Это запускает:

docker compose down

По умолчанию docker compose down удаляет:

  • Контейнеры для сервисов в файле Compose
  • Сети, определенные в файле Compose
  • Сеть по умолчанию

Она не удаляет именованные тома, если вы не попросите об этом.

Не используйте небрежно:

docker compose down -v

Это удаляет именованные тома, объявленные в файле Compose, и анонимные тома, прикрепленные к контейнерам. Для баз данных и сервисов с состоянием это может означать удаление реальных данных.

Используйте down -v только когда вы имеете в виду “разрушить это окружение”.

Перезапуск сервиса

Перезапустите юнит systemd:

sudo systemctl restart myapp.service

Это запускает команду остановки, а затем команду запуска.

Для перезапуска только контейнеров без их пересоздания:

cd /opt/myapp
docker compose restart

Важное различие:

  • docker compose restart перезапускает существующие контейнеры.
  • docker compose up -d применяет изменения конфигурации или образов, пересоздавая контейнеры при необходимости.

Если вы изменили compose.yaml, используйте:

docker compose up -d

А не просто:

docker compose restart

Обработка сиротских контейнеров

Если вы переименовали или удалили сервис в compose.yaml, старые контейнеры могут остаться как сироты.

Используйте:

docker compose up -d --remove-orphans

Поэтому в примерах systemd-сервисов в этом руководстве используется:

ExecStart=/usr/bin/docker compose up -d --remove-orphans

Это держит стек ближе к текущему файлу Compose.

Резервное копирование

Резервное копирование зависит от рабочей нагрузки, но принципы стабильны.

Для bind mounts:

/opt/myapp/data/

Резервируйте эту директорию.

Для именованных томов:

docker volume ls

Осмотрите том:

docker volume inspect volume-name

Для баз данных копий файловой системы не всегда достаточно. Используйте резервное копирование, осознающее приложение:

Пример PostgreSQL:

docker compose exec -T db pg_dump -U postgres appdb > backups/appdb.sql

Пример MariaDB:

docker compose exec -T db mariadb-dump -u root -p appdb > backups/appdb.sql

Пример Redis:

docker compose exec redis redis-cli BGSAVE

Стек Compose без плана резервного копирования — это не сервис, а временный эксперимент, который случайно имеет uptime.

Базовая безопасность

Для небольшого сервиса Compose на Linux начните с этой базы:

  • Держите проект Compose под /opt/appname.
  • Используйте явные теги образов, а не только latest, когда важна стабильность.
  • Используйте bind mounts или именованные тома осознанно.
  • Не экспортируйте порты, которые вам не нужны.
  • Поместите публичные сервисы за обратный прокси.
  • Используйте HTTPS на границе.
  • Держите секреты вне Git.
  • Ограничьте права доступа к .env.
  • Избегайте привилегированных контейнеров, если они не требуются по-настоящему.
  • Избегайте монтирования сокета Docker в контейнеры.
  • Держите Docker и образы обновленными.
  • Тестируйте поведение файрвола с другой машины.

Опасный паттерн:

volumes:
  - /var/run/docker.sock:/var/run/docker.sock

Это дает контейнеру контроль над Docker. На практике это может стать контролем на уровне хоста. Используйте это только когда вы понимаете риск.

Ограничения ресурсов

На небольших серверах один плохой контейнер может потребить хост.

Compose поддерживает настройки, связанные с ресурсами, но поведение может зависеть от версии Docker Engine и Compose. Для простой защиты начните с ограничений на уровне приложения и ограничений логов Docker.

Для некоторых рабочих нагрузок вы можете добавить ограничения памяти:

services:
  app:
    image: example/app:stable
    restart: unless-stopped
    mem_limit: 512m

Также настройте количество воркеров на уровне приложения, лимиты очередей и размеры кэша. Ограничения контейнеров полезны, но они не заменяют понимание приложения.

Пример: Реалистичный сервис Compose

Директория:

/opt/whoami/
  compose.yaml
  .env

Файл Compose:

services:
  whoami:
    image: traefik/whoami:v1.10
    restart: unless-stopped
    ports:
      - "${WHOAMI_PORT}:80"
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://localhost || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3

Файл .env:

WHOAMI_PORT=8080
COMPOSE_PROJECT_NAME=whoami

Юнит systemd:

[Unit]
Description=Whoami Docker Compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/whoami
ExecStartPre=/usr/bin/docker compose config --quiet
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecReload=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
TimeoutStopSec=120

[Install]
WantedBy=multi-user.target

Установите его:

sudo systemctl daemon-reload
sudo systemctl enable --now whoami.service

Протестируйте:

curl http://localhost:8080

Проверьте статус:

systemctl status whoami.service
cd /opt/whoami
docker compose ps

Устранение неполадок

Сервис запускается, но контейнеры не работают

Проверьте systemd:

journalctl -u myapp.service -n 100 --no-pager

Валидируйте Compose:

cd /opt/myapp
docker compose config

Проверьте Docker:

systemctl status docker
docker info

WorkingDirectory неверный

Если systemd не может найти ваш файл Compose, подтвердите:

WorkingDirectory=/opt/myapp

Затем проверьте:

ls -la /opt/myapp
ls -la /opt/myapp/compose.yaml

Сервис запускается из WorkingDirectory, а не из текущей директории оболочки.

Отказано в доступе Docker

Если юнит запускается как root, он обычно может получить доступ к Docker.

Если вы установили User=someuser, этот пользователь должен иметь доступ к Docker. Обычно это означает членство в группе docker или настройку Docker без прав root.

Проверьте:

groups someuser

Добавьте пользователя, если это уместно:

sudo usermod -aG docker someuser

Будьте осторожны. Группа Docker фактически привилегирована.

Команда Compose не найдена

Найдите Docker:

command -v docker

Используйте полный путь в юните:

ExecStart=/usr/bin/docker compose up -d --remove-orphans

Если плагин Compose отсутствует:

docker compose version

Установите его через ваш источник пакетов Docker.

Переменные окружения отсутствуют

Проверьте конфигурацию Compose так, как видит ее systemd:

cd /opt/myapp
docker compose config

Если systemd нужны дополнительные переменные окружения, используйте:

EnvironmentFile=-/opt/myapp/.env.systemd

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

/opt/myapp/.env

Они связаны, но не идентичны.

Контейнеры не запускаются после перезагрузки

Проверьте, включен ли сервис systemd:

systemctl is-enabled myapp.service

Включите его:

sudo systemctl enable myapp.service

Проверьте Docker:

systemctl is-enabled docker
systemctl status docker

Проверьте логи загрузки:

journalctl -u myapp.service -b --no-pager

Приложение запускается до того, как база данных готова

Добавьте health check базы данных и depends_on с service_healthy.

Также исправьте приложение. Оно должно повторять попытки соединения с базой данных. Порядок запуска инфраструктуры полезен, но логика повторных попыток в приложении лучше.

Диск заполнен логами Docker

Проверьте использование диска Docker:

docker system df

Проверьте большие логи контейнеров:

sudo du -h /var/lib/docker/containers | sort -h | tail

Настройте ротацию логов Docker в /etc/docker/daemon.json.

Затем пересоздайте контейнеры.

Частые ошибки

Ошибка 1: Запуск docker compose up в rc.local

Запуск docker compose up из rc.local или скрипта входа работает, пока не перестанет — используйте правильный юнит systemd вместо этого.

Ошибка 2: Использование Restart=always в systemd и restart: always в Compose

Обычно вам нужны только политики перезапуска контейнеров в Compose. Избегайте борьбы двух супервизоров.

Ошибка 3: Забывание –remove-orphans

Переименования и удаления сервисов могут оставить старые контейнеры. Используйте:

docker compose up -d --remove-orphans

Ошибка 4: Использование docker compose restart после изменений конфигурации

restart перезапускает контейнеры. Он не применяет все изменения конфигурации.

Используйте:

docker compose up -d

Ошибка 5: Запуск down -v без раздумий

Это может удалить тома. Для сервисов с состоянием это может означать удаление данных.

Ошибка 6: Нет резервной копии перед pull

Новые образы могут сломаться. Базы данных могут мигрировать. Теги могут сдвинуться. Сначала сделайте резервную копию.

Ошибка 7: Публикация каждого порта

Публикуйте только то, что хосту нужно экспортировать. Внутренний трафик между сервисами может оставаться в сети Compose.

Итоговый рекомендуемый паттерн

Для большинства сервисов на одном Linux-хосте используйте этот паттерн:

Файл Compose:

services:
  app:
    image: example/app:stable
    restart: unless-stopped
    ports:
      - "8080:8080"
    env_file:
      - .env

Юнит systemd:

[Unit]
Description=MyApp Docker Compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/myapp
ExecStartPre=/usr/bin/docker compose config --quiet
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecReload=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
TimeoutStopSec=120

[Install]
WantedBy=multi-user.target

Включите его:

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

Эксплуатируйте его:

sudo systemctl status myapp.service
sudo systemctl restart myapp.service
journalctl -u myapp.service -f
cd /opt/myapp && docker compose logs -f

Этот паттерн не фантичный, и это суть. Docker Compose отлично подходит для небольших, понятных систем, systemd отлично справляется с запуском и остановкой сервисов хоста, и вместе они дают вам надежную модель развертывания на одном сервере, не притворяясь, что каждому проекту нужен кластер. Для команд уровня контейнеров вне Compose — образы, тома, сети и очистка — см. Шпаргалку по Docker.

Подписаться

Получайте новые материалы про системы, инфраструктуру и AI engineering.