Uruchamianie Docker Compose jako usługa Linuxa za pomocą systemd

Docker Compose uruchamiany przy starcie systemu, zarządzany przez systemd.

Page content

Skrypt Docker Compose na serwerze Linux powinien uruchamiać się przy starcie systemu, zatrzymywać się poprawnie przy wyłączaniu oraz przetrwać ponowne uruchomienia bez interwencji człowieka.

Docker Compose nie jest Kubernetes, a dla obciążeń, na które celuje ten przewodnik, jest to w porządku. Dla wielu rzeczywistych systemów projekt Compose na pojedynczym hoście Linux stanowi odpowiednią ilość infrastruktury — jest prosty, czytelny, łatwy do kopii zapasowych oraz wystarczający dla wewnętrznych narzędzi, projektów pobocznych, usług self-hosted, środowisk stagingowych, małych aplikacji produkcyjnych oraz infrastruktury deweloperskiej.

docker compose config ont the table with laptop

Brakującym elementem jest zazwyczaj zarządzanie usługami. Uruchamianie tego ręcznie nie wystarczy:

docker compose up -d

Pojedyncze polecenie uruchamia stos, ale nie dokumentuje, jak stos powinien startować przy bootowaniu, zatrzymywać się podczas wyłączania, przeładowywać się po zmianach, zapisywać logi, odzyskiwać się po awariach czy bezpiecznie aktualizować. Tutaj z pomocą przychodzi systemd.

Ten przewodnik przeprowadza przez uruchamianie projektu Docker Compose jako usługi Linux za pomocą systemd — pliki jednostek, kolejność startową, aktualizacje, logi oraz kopie zapasowe. Podział odpowiedzialności jest celowy: Docker uruchamia kontenery, Compose definiuje stos, a systemd uruchamia i zatrzymuje projekt na hoście. Jest to część Narzędzia Deweloperskie - Przewodnik po Procesach Deweloperskich.

Kiedy Docker Compose jako Usługa Ma Sens

Uruchamianie Compose pod systemd ma sens, gdy posiadasz:

  • Pojedynczy serwer Linux
  • Małą aplikację self-hosted
  • Stos proxy odwrotnego
  • Stos monitoringu
  • Lokalną platformę deweloperską
  • Narzędzie wewnętrzne
  • Środowisko stagingowe
  • Prostą usługę produkcyjną z znanymi limitami

Przykłady:

  • Nginx Proxy Manager
  • Traefik
  • Gitea
  • Grafana i Prometheus
  • PostgreSQL oraz mała aplikacja webowa
  • Uptime Kuma
  • Usługi pomocnicze Home Assistant
  • Prywatny rejestr obrazów
  • Wewnętrzne API, worker oraz Redis

Compose jest dobrym wyborem, gdy model operacyjny jest nadal zrozumiały dla jednej osoby przeglądającej jeden katalog.

Kiedy Docker Compose Nie Wystarczy

Należy użyć czegoś innego, gdy potrzebujesz:

  • Harmonogramowania na wielu węzłach
  • Automatycznego ponownego harmonogramowania między hostami
  • Odkrywania usług na poziomie klastra
  • Poziomego autoskalowania
  • Wdrożeń rollingowych na wielu maszynach
  • Precyzyjnej tożsamości obciążeń
  • Skomplikowanej polityki sieciowej
  • Operacji platformowych dla wielu dużych zespołów

W tym momencie Kubernetes, Nomad, Swarm lub zarządzana platforma mogą być lepszym wyborem.

Moje praktyczne zasady to: unikanie używania Kubernetes tylko po to, aby ominąć naukę systemd, oraz unikanie używania Compose, gdy obciążenie wyraźnie wymaga orkiestracji między wieloma hostami.

Podstawowa Architektura

Czysta konfiguracja oddziela pliki projektu, jednostkę systemd oraz dane trwałego magazynu na hoście. Projekt Compose znajduje się pod ścieżką /opt/myapp/ z plikami compose.yaml, .env, data/, backups/ oraz opcjonalnymi skryptami, takimi jak scripts/update.sh. Plik jednostki systemd znajduje się w /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

Każda warstwa ma jasno zdefiniowane zadanie: Docker uruchamia kontenery, Compose definiuje stos aplikacji, systemd uruchamia i zatrzymuje projekt Compose przy starcie i wyłączaniu, system plików hosta przechowuje trwałe dane, kopie zapasowe pozostają jawne, a aktualizacje przechodzą przez skryptowane, możliwe do weryfikacji kroki. Ten układ jest celowo nudny, ponieważ nudna infrastruktura jest łatwiejsza do naprawy, gdy coś się zepsuje o 2 rano.

Przygotowanie Katalogu Projektu Compose

Utwórz katalog pod ścieżką /opt:

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

Utwórz plik Compose:

nano compose.yaml

Przykład:

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: {}

Utwórz katalog treści:

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

Najpierw przetestuj ręcznie:

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

Następnie zatrzymaj go przed przekazaniem cyklu życia systemd:

docker compose down

Nie twórz usługi systemd, dopóki projekt Compose nie będzie działać ręcznie. Podczas testowania trzymaj pod ręką Cheat-sheet Docker Compose dla komend ps, logs, pull oraz struktury projektu.

Użyj Nowoczesnej Komendy docker compose

Docker Engine oraz wtyczka Compose muszą być zainstalowane przed napisaniem pliku jednostki. Na Ubuntu, Instalacja Dockera na Ubuntu przeprowadza przez APT, Snap, tryb bez uprawnień root oraz bezpieczeństwo po instalacji, abyś miał działającą komendę docker compose.

Używaj tego:

docker compose version

Nie tego:

docker-compose version

Stary binarny plik docker-compose nadal istnieje na wielu maszynach, ale nowoczesny Docker używa Compose jako wtyczki do Dockera CLI.

W plikach usług oraz skryptach preferuj:

/usr/bin/docker compose

Możesz znaleźć ścieżkę do Dockera za pomocą:

command -v docker

Zazwyczaj jest to:

/usr/bin/docker

Tworzenie Usługi systemd dla Docker Compose

Jeśli pliki jednostek są Ci nieznane, Uruchomienie Dowolnego Wykonywalnego Pliku jako Usługi w Linux wyjaśnia Type, ExecStart, systemctl oraz ogólny przepływ pracy systemd. Ta sekcja stosuje te wzorce specyficznie do stosu Compose.

Utwórz plik usługi:

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

Użyj tej jednostki:

[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

Przeładuj systemd:

sudo systemctl daemon-reload

Uruchom usługę:

sudo systemctl start myapp.service

Włącz ją przy starcie:

sudo systemctl enable myapp.service

Sprawdź status:

systemctl status myapp.service

Sprawdź kontenery:

cd /opt/myapp
docker compose ps

Dlaczego Type=oneshot i RemainAfterExit=yes?

To jest ta część, którą wiele przewodników robi subtelnie błędnie.

docker compose up -d uruchamia kontenery w trybie odłączonym i kończy swoje działanie, więc nie ma długotrwałego procesu Compose w pierwszym planie, którego systemd miałby nadzorować. Jednostka systemd nie powinna udawać, że docker compose up -d jest długotrwałym demonem.

Użyj:

Type=oneshot
RemainAfterExit=yes

To informuje systemd:

  • Uruchom komendę startową.
  • Traktuj jednostkę jako aktywną po pomyślnym zakończeniu komendy.
  • Uruchom ExecStop, gdy usługa zostanie zatrzymana.

To pasuje do rzeczywistego zachowania odłączonego Compose, dlatego Type=oneshot z RemainAfterExit=yes jest prawidłowym domyślnym wyborem dla większości stosów.

Dlaczego Nie Type=simple?

Z Type=simple, systemd oczekuje, że proces ExecStart będzie kontynuował działanie, ale docker compose up -d kończy swoje działanie po uruchomieniu kontenerów. Może to sprawić, że systemd uzna, że usługa się zakończyła, a następnie wywoła logikę zatrzymania lub oznaczy jednostkę jako nieaktywną w zależności od konfiguracji.

Jeśli chcesz Type=simple, zazwyczaj uruchomiłbyś Compose w pierwszym planie:

ExecStart=/usr/bin/docker compose up

To może działać, ale zazwyczaj nie preferuję tego dla stosów Compose na serwerach. Odłączone kontenery wraz z jawnym ExecStop są łatwiejsze w obsłudze.

Jednostka Przyjaźniejsza do Produkcji

Dla prawdziwego serwera preferuję nieco bardziej rygorystyczną jednostkę:

[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

Ważne szczegóły:

  • WorkingDirectory wskazuje na projekt Compose.
  • ExecStartPre waliduje konfigurację Compose.
  • ExecReload odtwarza zmienione usługi.
  • ExecStop zatrzymuje i usuwa kontenery projektu Compose oraz domyślną sieć.
  • EnvironmentFile=-... oznacza, że plik jest opcjonalny.

Utwórz opcjonalny plik środowiska systemd:

nano /opt/myapp/.env.systemd

Przykład:

COMPOSE_PROJECT_NAME=myapp

Następnie przeładuj systemd:

sudo systemctl daemon-reload
sudo systemctl restart myapp.service

Compose .env vs systemd EnvironmentFile

Compose i systemd mają własne mechanizmy środowiskowe, a mieszanie ich powoduje mylące błędy “zmienna nie ustawiona” przy starcie.

Compose automatycznie odczytuje plik .env w katalogu projektu w celu podstawienia zmiennych w pliku Compose.

Przykład .env:

APP_TAG=1.2.3
WEB_PORT=8080

Przykład compose.yaml:

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

EnvironmentFile systemd ustawia zmienne środowiskowe dla samej komendy docker compose.

Przykład:

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

Dla wielu projektów potrzebujesz tylko Compose .env.

Użyj pliku środowiska systemd, gdy chcesz zdefiniować takie rzeczy jak:

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

Nie używaj żadnego z tych plików jako swobodnej skarbca sekretów. Jeśli sekrety są ważne, użyj Docker secrets, zewnętrznego menedżera sekretów, zaszyfrowanych plików lub co najmniej rygorystycznych uprawnień.

Ustaw rygorystyczne uprawnienia:

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

Polityki Restartu: Docker vs systemd

Istnieją dwie warstwy restartu — polityka restartu kontenera w Compose oraz polityka restartu usługi systemd — i nie powinny być one mieszane oślepnie.

Dla długotrwałych kontenerów ustaw polityki restartu w Compose:

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

Najczęstsze wartości restartu:

Polityka Znaczenie
no Nie restartuj automatycznie
always Restartuj po wyjściu i restarcie demona
on-failure Restartuj tylko po awarii
unless-stopped Restartuj, chyba że zatrzymano ręcznie

Dla większości trwałych usług preferuję:

restart: unless-stopped

Jest to przewidywalne i szanuje intencjonalne ręczne zatrzymania.

Sama jednostka systemd zazwyczaj nie powinna się wielokrotnie restartować, ponieważ docker compose up -d nie jest działającym obciążeniem. Kontenery są obciążeniem.

Dlatego unikaj tego, chyba że masz konkretny powód:

Restart=always

W większości jednostek Compose-as-service pozwól Dockerowi obsługiwać restarty kontenerów.

Testy Zdrowia (Health Checks)

Polityki restartu restartują kontenery, gdy procesy kończą działanie. Nie naprawiają magicznie każdej nietypowej aplikacji.

Dodaj testy zdrowia tam, gdzie są przydatne:

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

Sprawdź zdrowie:

docker compose ps

Zbadaj kontener:

docker inspect container-name

Testy zdrowia są szczególnie przydatne dla:

  • Aplikacji webowych
  • Proxy odwrotnych
  • Baz danych
  • Kolejek
  • Wewnętrznych API
  • Workerów z punktem końcowym zdrowia

Są mniej przydatne, gdy sprawdzają tylko, czy proces istnieje, ponieważ proces, który żyje, ale jest zawieszony, nadal wygląda na zdrowy. Zły test zdrowia to tylko kolejne kłamstwo w YAML.

Kolejność Startu i depends_on

Compose może definiować zależności:

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

To może pomóc w kolejności startu, ale nie ufaj temu nadmiernie. Aplikacje powinny nadal obsługiwać powtórzenia — bazy danych się restartują, sieci się urywają, DNS potrzebuje czasu, a odporna aplikacja powtarza połączenia zamiast zakładać idealną kolejność startową.

Logi: journalctl i docker compose logs

Dwa widoki logów pokrywają większość debugowania: systemd przechwytuje cykl życia samej jednostki, podczas gdy Compose przechwytuje wyjście aplikacji z działających kontenerów.

Logi usługi systemd:

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

Śledź logi systemd:

journalctl -u myapp.service -f

Logi usługi Compose:

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

Dla większości debugowania aplikacji docker compose logs jest bardziej przydatne; dla debugowania cyklu życia — błędy startu, awarie jednostek, błędy uprawnień — journalctl jest bardziej przydatne. Jeśli systemctl start myapp się nie powiedzie, sprawdź najpierw journalctl. Jeśli stos się uruchomi, ale aplikacja jest uszkodzona, sprawdź docker compose logs.

Rotacja Logów

Logi Dockera mogą rosnąć w nieskończoność, jeśli ich nie skonfigurujesz.

Dla małych serwerów skonfiguruj rotację logów Dockera w /etc/docker/daemon.json:

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

Restartuj Dockera:

sudo systemctl restart docker

Następnie restartuj stos Compose:

sudo systemctl restart myapp.service

Dotyczy to nowo utworzonych kontenerów. Odtwórz kontenery, jeśli to konieczne:

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

Rotacja logów nie jest glamorous, ale to jeden z najłatwiejszych sposobów zapobiegania awarii z powodu pełnego dysku na małym serwerze.

Aktualizacja Usługi Compose

Prosty ręczny przepływ aktualizacji:

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

Jeśli zarządzane przez systemd, możesz użyć:

sudo systemctl reload myapp.service

Jeśli Twoja jednostka ma:

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

Ale pamiętaj: ExecReload nie pobiera obrazów, chyba że dołączysz ten krok.

Dla jawnych aktualizacji utwórz skrypt.

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

Skrypt:

#!/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

Spraw go wykonywalny:

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

Uruchom go:

/opt/myapp/scripts/update.sh

Następnie jednostka usługi może pozostać skupiona na cyklu życia, podczas gdy skrypt aktualizacji obsługuje wdrożenie.

Bezpieczniejszy Skrypt Aktualizacji z Hakiem Kopii Zapasowej

Dla usług z stanem, aktualizuj dopiero po kopii zapasowej.

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

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

cd "$APP_DIR"

mkdir -p "$BACKUP_DIR"

echo "Walidacja pliku compose"
docker compose config --quiet

echo "Uruchamianie haki kopii zapasowej"
if [ -x "$APP_DIR/scripts/backup.sh" ]; then
  "$APP_DIR/scripts/backup.sh"
else
  echo "Nie znaleziono haki kopii zapasowej"
fi

echo "Pobieranie obrazów"
docker compose pull

echo "Odtwarzanie usług"
docker compose up -d --remove-orphans

echo "Usuwanie nieużywanych obrazów"
docker image prune -f

echo "Obecny status"
docker compose ps

To nadal jest proste, ale teraz koduje nawyk operacyjny: kopia zapasowa przed zmianą.

Zatrzymywanie Usługi

Zatrzymaj stos:

sudo systemctl stop myapp.service

To uruchamia:

docker compose down

Domyślnie docker compose down usuwa:

  • Kontenery dla usług w pliku Compose
  • Sieci zdefiniowane przez plik Compose
  • Domyślną sieć

Nie usuwa nazwanych wolumenów, chyba że o to poprosisz.

Nie używaj swobodnie:

docker compose down -v

To usuwa nazwane wolumeny zadeklarowane w pliku Compose oraz anonimowe wolumeny podłączone do kontenerów. Dla baz danych i aplikacji z stanem może to oznaczać usunięcie prawdziwych danych.

Używaj down -v tylko wtedy, gdy masz na myśli “zniszcz to środowisko”.

Restartowanie Usługi

Restartuj jednostkę systemd:

sudo systemctl restart myapp.service

To uruchamia komendę stop, a następnie komendę start.

Dla samego restartu kontenerów bez ich odtwarzania:

cd /opt/myapp
docker compose restart

Wažne rozróżnienie:

  • docker compose restart restartuje istniejące kontenery.
  • docker compose up -d stosuje zmiany konfiguracji lub obrazów, odtwarzając kontenery, gdy to konieczne.

Jeśli zmieniłeś compose.yaml, użyj:

docker compose up -d

Nie tylko:

docker compose restart

Obsługa Kontenerów Siotków (Orphan Containers)

Jeśli zmienisz nazwę lub usuniesz usługę w compose.yaml, stare kontenery mogą pozostać jako siotki.

Użyj:

docker compose up -d --remove-orphans

Dlatego przykłady usług systemd w tym przewodniku używają:

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

To utrzymuje stos bliżej bieżącego pliku Compose.

Kopie Zapasowe

Kopie zapasowe zależą od obciążenia, ale zasady są stabilne.

Dla montowań bind:

/opt/myapp/data/

Zrób kopię tego katalogu.

Dla nazwanych wolumenów:

docker volume ls

Zbadaj wolumin:

docker volume inspect volume-name

Dla baz danych, kopie systemu plików nie są zawsze wystarczające. Użyj kopii zapasowych świadomych aplikacji:

Przykład PostgreSQL:

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

Przykład MariaDB:

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

Przykład Redis:

docker compose exec redis redis-cli BGSAVE

Stos Compose bez planu kopii zapasowych nie jest usługą — to tymperymentalny eksperyment, który przypadkiem ma czas uptime.

Podstawy Bezpieczeństwa

Dla małej usługi Compose na Linux, zacznij od tej podstawy:

  • Trzymaj projekt Compose pod /opt/appname.
  • Używaj jawnych tagów obrazów, nie tylko latest, gdy stabilność ma znaczenie.
  • Używaj montowań bind lub nazwanych wolumenów świadomie.
  • Nie eksponuj portów, których nie potrzebujesz.
  • Umieść usługi publiczne za proxy odwrotnym.
  • Używaj HTTPS na krawędzi.
  • Trzymaj sekrety z dala od Gita.
  • Ogranicz uprawnienia .env.
  • Unikaj kontenerów przywilejowych, chyba że są naprawdę wymagane.
  • Unikaj montowania gniazda Dockera do kontenerów.
  • Trzymaj Dockera i obrazy zaktualizowane.
  • Testuj zachowanie zapory ogniowej z innej maszyny.

Niebezpieczny wzorzec:

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

To daje kontenerowi kontrolę nad Dockerem. W praktyce może to stać się kontrolą na poziomie hosta. Używaj tego tylko wtedy, gdy rozumiesz ryzyko.

Limity Zasobów

Na małych serwerach jeden zły kontener może pochłonąć hosta.

Compose obsługuje ustawienia związane z zasobami, ale zachowanie może zależeć od wersji Dockera Engine i Compose. Dla prostej ochrony zacznij od limitów na poziomie aplikacji i limitów logowania Dockera.

Dla niektórych obciążeń możesz dodać limity pamięci:

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

Skonfiguruj również liczbę workerów na poziomie aplikacji, limity kolejek oraz rozmiary cache. Limity kontenerów są przydatne, ale nie zastępują zrozumienia aplikacji.

Przykład: Realistyczna Usługa Compose

Katalog:

/opt/whoami/
  compose.yaml
  .env

Plik 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

Plik .env:

WHOAMI_PORT=8080
COMPOSE_PROJECT_NAME=whoami

Jednostka 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

Zainstaluj ją:

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

Przetestuj:

curl http://localhost:8080

Sprawdź status:

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

Rozwiązywanie Problemów

Usługa Startuje, Ale Kontenery Nie Działają

Sprawdź systemd:

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

Zwaliduj Compose:

cd /opt/myapp
docker compose config

Sprawdź Dockera:

systemctl status docker
docker info

WorkingDirectory Jest Niewłaściwe

Jeśli systemd nie może znaleźć Twojego pliku Compose, potwierdź:

WorkingDirectory=/opt/myapp

Następnie sprawdź:

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

Usługa działa z WorkingDirectory, nie z Twojego bieżącego katalogu powłoki.

Docker Permission Denied

Jeśli jednostka działa jako root, normalnie ma dostęp do Dockera.

Jeśli ustawisz User=someuser, ten użytkownik musi mieć dostęp do Dockera. Zazwyczaj oznacza to przynależność do grupy docker lub konfigurację Dockera bez uprawnień root.

Sprawdź:

groups someuser

Dodaj użytkownika, jeśli to odpowiednie:

sudo usermod -aG docker someuser

Bądź ostrożny. Grupa Docker jest efektywnie przywilejowana.

Komenda Compose Nie Znaleziona

Znajdź Dockera:

command -v docker

Użyj pełnej ścieżki w jednostce:

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

Jeśli wtyczka Compose jest brakująca:

docker compose version

Zainstaluj ją używając swojego źródła pakietów Dockera.

Brakujące Zmienne Środowiskowe

Sprawdź konfigurację Compose tak, jakby widział ją systemd:

cd /opt/myapp
docker compose config

Jeśli systemd potrzebuje dodatkowych zmiennych środowiskowych, użyj:

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

Jeśli Compose potrzebuje zmiennych do podstawienia, użyj:

/opt/myapp/.env

Są one powiązane, ale nie identyczne.

Kontenery Nie Startują Po Ponownym Uruchomieniu

Sprawdź, czy usługa systemd jest włączona:

systemctl is-enabled myapp.service

Włącz ją:

sudo systemctl enable myapp.service

Sprawdź Dockera:

systemctl is-enabled docker
systemctl status docker

Sprawdź logi bootowania:

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

Aplikacja Startuje Przed Gotowością Bazy Danych

Dodaj test zdrowia bazy danych oraz depends_on z service_healthy.

Napraw również aplikację. Powinna ona powtarzać połączenia z bazą danych. Kolejność startu infrastruktury jest pomocna, ale logika powtórzeń aplikacji jest lepsza.

Dysk Pełny Logami Dockera

Sprawdź zużycie dysku przez Dockera:

docker system df

Sprawdź duże logi kontenerów:

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

Skonfiguruj rotację logów Dockera w /etc/docker/daemon.json.

Następnie odtwórz kontenery.

Częste Błędy

Błąd 1: Uruchamianie docker compose up w rc.local

Uruchamianie docker compose up z rc.local lub skryptu logowania działa, dopóki nie przestanie — użyj odpowiedniej jednostki systemd zamiast tego.

Błąd 2: Używanie Restart=always w systemd oraz restart: always w Compose

Zazwyczaj potrzebujesz tylko polityk restartu kontenera w Compose. Unikaj dwóch nadzorców walczących ze sobą.

Błąd 3: Zapominanie o –remove-orphans

Zmiany nazw i usunięcia usług mogą zostawić stare kontenery. Użyj:

docker compose up -d --remove-orphans

Błąd 4: Używanie docker compose restart Po Zmianach Konfiguracji

restart restartuje kontenery. Nie stosuje wszystkich zmian konfiguracji.

Użyj:

docker compose up -d

Błąd 5: Uruchamianie down -v Bez Myślenia

To może usunąć wolumeny. Dla usług z stanem może to oznaczać usunięcie danych.

Błąd 6: Brak Kopii Zapasowej Przed Pull

Nowe obrazy mogą się zepsuć. Bazy danych mogą się migrować. Tagi mogą się przesuwać. Najpierw zrób kopię zapasową.

Błąd 7: Publikowanie Każdego Portu

Publikuj tylko to, co host musi eksponować. Ruch wewnętrzny między usługami może pozostać w sieci Compose.

Ostatecznie Rekomendowany Wzorzec

Dla większości usług Linux na pojedynczym hoście użyj tego wzorca:

Plik Compose:

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

Jednostka 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

Włącz ją:

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

Obsługuj ją:

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

Ten wzorzec nie jest skomplikowany, i to jest właśnie sedno sprawy. Docker Compose jest doskonały dla małych, zrozumiałych systemów, systemd jest doskonały w uruchamianiu i zatrzymywaniu usług hosta, a razem dają Ci niezawodny model wdrożenia na pojedynczym serwerze, nie udając, że każdy projekt potrzebuje klastra. Dla poleceń na poziomie kontenera poza Composem — obrazy, wolumeny, sieci oraz czyszczenie — zobacz Cheat-sheet Dockera.

Subskrybuj

Otrzymuj nowe wpisy o systemach, infrastrukturze i inżynierii AI.