Docker Compose als Linux-Dienst mit systemd ausführen

Docker Compose beim Systemstart, verwaltet durch systemd.

Inhaltsverzeichnis

Ein Docker Compose-Projekt auf einem Linux-Server sollte beim Booten starten, beim Herunterfahren sauber gestoppt werden und Neustarts ohne manuelle Eingriffe überstehen.

Docker Compose ist kein Kubernetes, und das ist für die Workloads, die dieser Leitfaden anspricht, auch in Ordnung. Für viele reale Systeme ist ein Compose-Projekt auf einem einzelnen Linux-Host die richtige Menge an Infrastruktur — einfach, lesbar, leicht zu sichern und gut genug für interne Tools, Nebenprojekte, selbst gehostete Dienste, Staging-Umgebungen, kleine Produktions-Apps und Entwicklerinfrastruktur.

docker compose config ont the table with laptop

Das fehlende Puzzleteil ist meist das Service-Management. Das manuelle Ausführen reicht nicht aus:

docker compose up -d

Ein einziger Befehl startet den Stack, dokumentiert aber nicht, wie der Stack beim Booten starten, während des Herunterfahrens stoppen, nach Änderungen neu laden, Logs schreiben, sich von Fehlern erholen oder sicher aktualisiert werden soll. Hier hilft systemd weiter.

Dieser Leitfaden beschreibt, wie man ein Docker Compose-Projekt als Linux-Service mit systemd betreibt — Einheiten-Dateien, Startreihenfolge, Updates, Logs und Backups. Die Trennung der Verantwortlichkeiten ist bewusst gewählt: Docker betreibt Container, Compose definiert den Stack und systemd startet und stoppt das Projekt auf dem Host. Es ist Teil von Entwickler-Tools – Ein Leitfaden für Entwicklungs-Workflows.

Wann Docker Compose als Service sinnvoll ist

Das Betreiben von Compose unter systemd macht Sinn, wenn Sie über Folgendes verfügen:

  • Einen einzelnen Linux-Server
  • Eine kleine, selbst gehostete Anwendung
  • Einen Reverse-Proxy-Stack
  • Einen Monitoring-Stack
  • Eine lokale Entwicklungsplattform
  • Ein internes Tool
  • Eine Staging-Umgebung
  • Einen einfachen Produktionsdienst mit bekannten Grenzen

Beispiele:

  • Nginx Proxy Manager
  • Traefik
  • Gitea
  • Grafana und Prometheus
  • PostgreSQL plus eine kleine Web-App
  • Uptime Kuma
  • Home Assistant Helper-Services
  • Private Registry
  • Interne API plus Worker plus Redis

Compose ist eine gute Wahl, wenn das Betriebsmodell noch von einer einzigen Person, die ein Verzeichnis liest, verständlich ist.

Wann Docker Compose nicht ausreicht

Nutzen Sie etwas anderes, wenn Sie Folgendes benötigen:

  • Multi-Node-Scheduling
  • Automatisches Rescheduling über Hosts hinweg
  • Service Discovery auf Clusterebene
  • Horizontales Autoscaling
  • Rolling Deployments über viele Maschinen hinweg
  • Feingranulare Workload-Identität
  • Komplexe Netzwerkrichtlinien
  • Große Plattformoperationen für mehrere Teams

An dieser Punkt können Kubernetes, Nomad, Swarm oder eine verwaltete Plattform besser passen.

Meine praktische Regel ist es, Kubernetes nicht nur zu verwenden, um das Lernen von systemd zu umgehen, und Compose nicht zu verwenden, wenn der Workload eindeutig Orchestrierung über mehrere Hosts hinweg benötigt.

Die grundlegende Architektur

Ein sauberer Aufbau trennt Projektdateien, die systemd-Einheit und persistente Daten auf dem Host. Das Compose-Projekt lebt unter /opt/myapp/ mit compose.yaml, .env, data/, backups/ und optionalen Skripten wie scripts/update.sh. Die systemd-Einheitsdatei befindet sich unter /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

Jede Schicht hat eine klare Aufgabe: Docker betreibt Container, Compose definiert den Anwendungsstack, systemd startet und stoppt das Compose-Projekt beim Booten und Herunterfahren, das Host-Dateisystem speichert persistente Daten, Backups bleiben explizit und Updates erfolgen über skriptgesteute, überprüfbare Schritte. Dieses Layout ist bewusst langweilig, denn langweilige Infrastruktur ist leichter zu reparieren, wenn um 2 Uhr nachts etwas bricht.

Das Compose-Projektverzeichnis vorbereiten

Erstellen Sie ein Verzeichnis unter /opt:

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

Erstellen Sie eine Compose-Datei:

nano compose.yaml

Beispiel:

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

Erstellen Sie das Inhaltsverzeichnis:

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

Testen Sie es zuerst manuell:

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

Stoppen Sie es dann, bevor Sie den Lebenszyklus an systemd übergeben:

docker compose down

Erstellen Sie keinen systemd-Service, solange das Compose-Projekt nicht manuell funktioniert. Behalten Sie während des Tests das Docker Compose Cheat-Sheet in der Nähe für ps, logs, pull und Projektstruktur.

Verwenden Sie den modernen docker compose-Befehl

Docker Engine und das Compose-Plugin müssen installiert sein, bevor Sie eine Einheitsdatei schreiben. Auf Ubuntu beschreibt Docker auf Ubuntu installieren APT, Snap, Rootless-Modus und Sicherheit nach der Installation, sodass Sie am Ende einen funktionierenden docker compose-Befehl haben.

Verwenden Sie dies:

docker compose version

Nicht dies:

docker-compose version

Die alte docker-compose-Binärdatei existiert immer noch auf vielen Maschinen, aber modernes Docker verwendet Compose als Docker-CLI-Plugin.

In Servicedateien und Skripten bevorzugen Sie:

/usr/bin/docker compose

Sie können den Docker-Pfad mit Folgendem finden:

command -v docker

Meistens ist es:

/usr/bin/docker

Einen systemd-Service für Docker Compose erstellen

Wenn Einheitsdateien neu für Sie sind, erklärt Jede ausführbare Datei als Service in Linux ausführen Type, ExecStart, systemctl und den allgemeinen systemd-Workflow. Dieser Abschnitt wendet diese Muster spezifisch auf einen Compose-Stack an.

Erstellen Sie die Servicedatei:

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

Verwenden Sie diese Einheit:

[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 neu laden:

sudo systemctl daemon-reload

Starten Sie den Service:

sudo systemctl start myapp.service

Aktivieren Sie ihn beim Booten:

sudo systemctl enable myapp.service

Status prüfen:

systemctl status myapp.service

Container prüfen:

cd /opt/myapp
docker compose ps

Warum Type=oneshot und RemainAfterExit=yes?

Das ist der Teil, den viele Leitfäden subtil falsch machen.

docker compose up -d startet Container im abgetrennten Modus und beendet sich, sodass es keinen lang laufenden Vordergrund-Compose-Prozess gibt, den systemd beaufsichtigen könnte. Die systemd-Einheit sollte nicht so tun, als wäre docker compose up -d ein lang laufender Daemon.

Verwenden Sie:

Type=oneshot
RemainAfterExit=yes

Dies sagt systemd:

  • Führen Sie den Startbefehl aus.
  • Betrachten Sie die Einheit als aktiv, nachdem der Befehl erfolgreich beendet wurde.
  • Führen Sie ExecStop aus, wenn der Service gestoppt wird.

Das entspricht dem tatsächlichen Verhalten von getrenntem Compose, weshalb Type=oneshot mit RemainAfterExit=yes der richtige Standard für die meisten Stacks ist.

Warum nicht Type=simple?

Mit Type=simple erwartet systemd, dass der ExecStart-Prozess weiterläuft, aber docker compose up -d beendet sich nach dem Starten der Container. Das kann dazu führen, dass systemd denkt, der Service sei beendet, und dann die Stop-Logik aufruft oder die Einheit als inaktiv markiert, je nach Konfiguration.

Wenn Sie Type=simple möchten, würden Sie Compose normalerweise im Vordergrund ausführen:

ExecStart=/usr/bin/docker compose up

Das kann funktionieren, aber ich bevorzuge es für Compose-Stacks auf Servern normalerweise nicht. Getrennte Container plus explizites ExecStop sind einfacher zu betreiben.

Eine produktionsfreundlichere Einheit

Für einen echten Server bevorzuge ich eine etwas strengere Einheit:

[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

Wichtige Details:

  • WorkingDirectory zeigt auf das Compose-Projekt.
  • ExecStartPre validiert die Compose-Konfiguration.
  • ExecReload erstellt geänderte Services neu.
  • ExecStop stoppt und entfernt die Compose-Projektcontainer und das Standardnetzwerk.
  • EnvironmentFile=-... bedeutet, dass die Datei optional ist.

Erstellen Sie die optionale systemd-Umgebungsdatei:

nano /opt/myapp/.env.systemd

Beispiel:

COMPOSE_PROJECT_NAME=myapp

Dann systemd neu laden:

sudo systemctl daemon-reload
sudo systemctl restart myapp.service

Compose .env vs. systemd EnvironmentFile

Compose und systemd haben jeweils ihren eigenen Umgebungsmechanismus, und das Vermischen führt zu verwirrenden „Variable nicht gesetzt"-Fehlern beim Booten.

Compose liest automatisch eine .env-Datei im Projektverzeichnis für Variablensubstitutionen in der Compose-Datei.

Beispiel .env:

APP_TAG=1.2.3
WEB_PORT=8080

Beispiel compose.yaml:

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

Eine systemd EnvironmentFile setzt Umgebungsvariablen für den docker compose-Befehl selbst.

Beispiel:

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

Für viele Projekte benötigen Sie nur die Compose-.env.

Verwenden Sie eine systemd-Umgebungsdatei, wenn Sie Dinge wie Folgendes definieren möchten:

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

Verwenden Sie keine dieser Dateien als lockeres Geheime-Vault. Wenn Geheimnisse wichtig sind, verwenden Sie Docker Secrets, einen externen Secret Manager, verschlüsselte Dateien oder zumindest strikte Berechtigungen.

Setzen Sie restriktive Berechtigungen:

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

Restart-Richtlinien: Docker vs. systemd

Es gibt zwei Restart-Ebenen — die Container-Restart-Richtlinie in Compose und die systemd-Service-Restart-Richtlinie — und diese sollten nicht blind gemischt werden.

Für lang laufende Container legen Sie Restart-Richtlinien in Compose fest:

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

Häufige Restart-Werte:

Richtlinie Bedeutung
no Nicht automatisch neu starten
always Nach Beendigung und Daemon-Neustart neu starten
on-failure Nur nach Fehler neu starten
unless-stopped Neu starten, es sei denn, manuell gestoppt

Für die meisten persistenten Services bevorzuge ich:

restart: unless-stopped

Es ist vorhersehbar und respektiert absichtliche manuelle Stops.

Die systemd-Einheit selbst sollte normalerweise nicht wiederholt neu starten, da docker compose up -d nicht der laufende Workload ist. Die Container sind es.

Vermeiden Sie also dies, es sei denn, Sie haben einen spezifischen Grund:

Restart=always

In den meisten Compose-as-Service-Einheiten lassen Sie Docker die Container-Neustarts handhaben.

Health Checks

Restart-Richtlinien starten Container neu, wenn Prozesse beendet werden. Sie beheben nicht magisch jede ungesunde Anwendung.

Fügen Sie Health Checks dort hinzu, wo sie nützlich sind:

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

Gesundheit prüfen:

docker compose ps

Einen Container inspizieren:

docker inspect container-name

Health Checks sind besonders nützlich für:

  • Web-Apps
  • Reverse Proxies
  • Datenbanken
  • Warteschlangen
  • Interne APIs
  • Worker mit einem Health-Endpoint

Sie sind weniger nützlich, wenn sie nur prüfen, ob ein Prozess existiert, denn ein Prozess, der lebt, aber blockiert ist, sieht immer noch gesund aus. Ein schlechter Health Check ist nur eine weitere Lüge in YAML.

Startreihenfolge und depends_on

Compose kann Abhängigkeiten definieren:

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

Das kann der Startreihenfolge helfen, aber vertrauen Sie nicht zu sehr darauf. Anwendungen sollten immer noch Retries handhaben — Datenbanken starten neu, Netzwerke flattern, DNS braucht Zeit, und eine resiliente App retryt Verbindungen, anstatt eine perfekte Startreihenfolge anzunehmen.

Logs: journalctl und docker compose logs

Zwei Log-Ansichten decken die meisten Debugging-Anforderungen ab: systemd fängt den Lebenszyklus der Einheit selbst auf, während Compose die Ausgabe der laufenden Container fängt.

systemd-Service-Logs:

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

systemd-Logs verfolgen:

journalctl -u myapp.service -f

Compose-Service-Logs:

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

Für die meisten App-Debugging-Zwecke ist docker compose logs nützlicher; für Lebenszyklus-Debugging — Startfehler, Einheitencrashs, Berechtigungsfehler — ist journalctl nützlicher. Wenn systemctl start myapp fehlschlägt, prüfen Sie zuerst journalctl. Wenn der Stack startet, aber die App defekt ist, prüfen Sie docker compose logs.

Log-Rotation

Docker-Logs können ewig wachsen, wenn Sie sie nicht konfigurieren.

Für kleine Server konfigurieren Sie die Docker-Log-Rotation in /etc/docker/daemon.json:

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

Docker neu starten:

sudo systemctl restart docker

Dann den Compose-Stack neu starten:

sudo systemctl restart myapp.service

Dies gilt für neu erstellte Container. Erstellen Sie Container bei Bedarf neu:

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

Log-Rotation ist nicht glamourös, aber es ist eine der einfachsten Möglichkeiten, eine Ausfallzeit aufgrund einer vollen Festplatte auf einem kleinen Server zu verhindern.

Aktualisieren eines Compose-Services

Ein einfacher manueller Update-Flow:

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

Wenn von systemd verwaltet, können Sie Folgendes verwenden:

sudo systemctl reload myapp.service

Wenn Ihre Einheit Folgendes hat:

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

Aber beachten Sie: ExecReload pulled Images nicht, es sei denn, Sie schließen diesen Schritt ein.

Für explizite Updates erstellen Sie ein Skript.

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

Skript:

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

Machen Sie es ausführbar:

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

Führen Sie es aus:

/opt/myapp/scripts/update.sh

Dann kann die Service-Einheit auf den Lebenszyklus fokussiert bleiben, während das Update-Skript die Bereitstellung handhabt.

Sichereres Update-Skript mit Backup-Hook

Für zustandsbehaftete Services nur nach Backup aktualisieren.

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

Das ist immer noch einfach, aber es kodiert jetzt eine betriebliche Gewohnheit: Backup vor Änderung.

Dienst stoppen

Den Stack stoppen:

sudo systemctl stop myapp.service

Das führt aus:

docker compose down

Standardmäßig entfernt docker compose down:

  • Container für Services in der Compose-Datei
  • Netzwerke, die in der Compose-Datei definiert sind
  • Das Standardnetzwerk

Es entfernt keine benannten Volumes, es sei denn, Sie bitten es darum.

Verwenden Sie nicht leichtsinnig:

docker compose down -v

Das entfernt benannte Volumes, die in der Compose-Datei deklariert sind, und anonyme Volumes, die an Container angehängt sind. Für Datenbanken und zustandsbehaftete Apps kann das bedeuten, dass echte Daten gelöscht werden.

Verwenden Sie down -v nur, wenn Sie „diese Umgebung zerstören" meinen.

Dienst neu starten

Die systemd-Einheit neu starten:

sudo systemctl restart myapp.service

Dies führt den Stop-Befehl und dann den Startbefehl aus.

Für das Neustarten nur der Container ohne Neuerstellung:

cd /opt/myapp
docker compose restart

Wichtige Unterscheidung:

  • docker compose restart startet existierende Container neu.
  • docker compose up -d wendet Konfigurations- oder Image-Änderungen an, indem es Container bei Bedarf neu erstellt.

Wenn Sie compose.yaml geändert haben, verwenden Sie:

docker compose up -d

Nicht nur:

docker compose restart

Unerwünschte Container handhaben

Wenn Sie einen Service in compose.yaml umbenennen oder entfernen, können alte Container als Waisen verbleiben.

Verwenden Sie:

docker compose up -d --remove-orphans

Deshalb verwenden die systemd-Service-Beispiele in diesem Leitfaden:

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

Es hält den Stack näher an der aktuellen Compose-Datei.

Backups

Backups hängen vom Workload ab, aber die Prinzipien sind stabil.

Für Bind-Mounts:

/opt/myapp/data/

Sichern Sie dieses Verzeichnis.

Für benannte Volumes:

docker volume ls

Ein Volume inspizieren:

docker volume inspect volume-name

Für Datenbanken sind Dateisystem-Kopien nicht immer ausreichend. Verwenden Sie anwendungsaware Backups:

PostgreSQL-Beispiel:

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

MariaDB-Beispiel:

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

Redis-Beispiel:

docker compose exec redis redis-cli BGSAVE

Ein Compose-Stack ohne Backup-Plan ist kein Service — es ist ein temporäres Experiment, das zufällig Uptime hat.

Sicherheitsbasis

Für einen kleinen Compose-Service auf Linux beginnen Sie mit dieser Basis:

  • Halten Sie das Compose-Projekt unter /opt/appname.
  • Verwenden Sie explizite Image-Tags, nicht nur latest, wenn Stabilität wichtig ist.
  • Verwenden Sie Bind-Mounts oder benannte Volumes bewusst.
  • Stellen Sie keine Ports frei, die Sie nicht benötigen.
  • Stellen Sie öffentliche Dienste hinter einen Reverse Proxy.
  • Verwenden Sie HTTPS an der Edge.
  • Halten Sie Geheimnisse aus Git fern.
  • Beschränken Sie .env-Berechtigungen.
  • Vermeiden Sie privilegierte Container, es sei denn, sie sind wirklich erforderlich.
  • Vermeiden Sie das Mounten des Docker-Sockets in Container.
  • Halten Sie Docker und Images auf dem neuesten Stand.
  • Testen Sie das Firewall-Verhalten von einer anderen Maschine aus.

Ein gefährliches Muster:

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

Dies gibt dem Container die Kontrolle über Docker. In der Praxis kann das zur Kontrolle auf Host-Ebene führen. Verwenden Sie es nur, wenn Sie das Risiko verstehen.

Ressourcenlimits

Auf kleinen Servern kann ein schlechter Container den Host verbrauchen.

Compose unterstützt ressourcenbezogene Einstellungen, aber das Verhalten kann von der Docker Engine und der Compose-Version abhängen. Für einfachen Schutz beginnen Sie mit Grenzen auf Anwendungsebene und Docker-Logging-Limits.

Für einige Workloads können Sie Speicherlimits hinzufügen:

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

Konfigurieren Sie auch Worker-Anzahlen, Warteschlangenlimits und Cache-Größen auf Anwendungsebene. Containerlimits sind nützlich, aber sie sind kein Ersatz für das Verständnis der Anwendung.

Beispiel: Ein realistischer Compose-Service

Verzeichnis:

/opt/whoami/
  compose.yaml
  .env

Compose-Datei:

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-Datei:

WHOAMI_PORT=8080
COMPOSE_PROJECT_NAME=whoami

systemd-Einheit:

[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

Installieren Sie es:

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

Testen:

curl http://localhost:8080

Status prüfen:

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

Fehlerbehebung

Service startet, aber Container laufen nicht

systemd prüfen:

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

Compose validieren:

cd /opt/myapp
docker compose config

Docker prüfen:

systemctl status docker
docker info

WorkingDirectory ist falsch

Wenn systemd Ihre Compose-Datei nicht finden kann, bestätigen Sie:

WorkingDirectory=/opt/myapp

Dann prüfen:

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

Der Service läuft von WorkingDirectory, nicht von Ihrem aktuellen Shell-Verzeichnis.

Docker Berechtigung verweigert

Wenn die Einheit als Root läuft, kann sie normalerweise auf Docker zugreifen.

Wenn Sie User=someuser setzen, muss dieser Benutzer auf Docker zugreifen können. Das bedeutet normalerweise die Mitgliedschaft in der docker-Gruppe oder ein rootless Docker-Setup.

Prüfen:

groups someuser

Den Benutzer hinzufügen, falls angemessen:

sudo usermod -aG docker someuser

Seien Sie vorsichtig. Die Docker-Gruppe ist effektiv privilegiert.

Compose-Befehl nicht gefunden

Docker finden:

command -v docker

Verwenden Sie den vollständigen Pfad in der Einheit:

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

Wenn das Compose-Plugin fehlt:

docker compose version

Installieren Sie es über Ihre Docker-Paketquelle.

Umgebungsvariablen fehlen

Prüfen Sie die Compose-Konfiguration so, wie systemd sie sehen würde:

cd /opt/myapp
docker compose config

Wenn systemd zusätzliche Umgebungsvariablen benötigt, verwenden Sie:

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

Wenn Compose Variablen für Substitutionen benötigt, verwenden Sie:

/opt/myapp/.env

Diese sind verwandt, aber nicht identisch.

Container starten nach Neustart nicht

Prüfen Sie, ob der systemd-Service aktiviert ist:

systemctl is-enabled myapp.service

Aktivieren Sie es:

sudo systemctl enable myapp.service

Docker prüfen:

systemctl is-enabled docker
systemctl status docker

Boot-Logs prüfen:

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

App startet, bevor Datenbank bereit ist

Fügen Sie einen Datenbank-Health-Check und depends_on mit service_healthy hinzu.

Reparieren Sie auch die Anwendung. Sie sollte Datenbankverbindungen retryen. Infrastruktur-Startreihenfolge ist hilfreich, aber Anwendungs-Retry-Logik ist besser.

Festplatte mit Docker-Logs gefüllt

Docker-Festplattenbelegung prüfen:

docker system df

Große Container-Logs prüfen:

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

Docker-Log-Rotation in /etc/docker/daemon.json konfigurieren.

Dann Container neu erstellen.

Häufige Fehler

Fehler 1: docker compose up in rc.local ausführen

Das Ausführen von docker compose up aus rc.local oder einem Login-Skript funktioniert, bis es nicht funktioniert — verwenden Sie stattdessen eine ordnungsgemäße systemd-Einheit.

Fehler 2: Restart=always in systemd und restart: always in Compose verwenden

Normalerweise benötigen Sie nur Container-Restart-Richtlinien in Compose. Vermeiden Sie, dass zwei Supervisoren gegeneinander kämpfen.

Fehler 3: –remove-orphans vergessen

Service-Umbenennungen und -Entfernungen können alte Container zurücklassen. Verwenden Sie:

docker compose up -d --remove-orphans

Fehler 4: docker compose restart nach Konfigurationsänderungen verwenden

restart startet Container neu. Es wendet nicht alle Konfigurationsänderungen an.

Verwenden Sie:

docker compose up -d

Fehler 5: down -v ohne Nachdenken ausführen

Dies kann Volumes löschen. Für zustandsbehaftete Services kann das bedeuten, dass Daten gelöscht werden.

Fehler 6: Kein Backup vor Pull

Neue Images können brechen. Datenbanken können migrieren. Tags können sich bewegen. Zuerst backupen.

Fehler 7: Jeden Port veröffentlichen

Nur das veröffentlichen, was der Host freizulegen benötigt. Interner Service-zu-Service-Verkehr kann im Compose-Netzwerk bleiben.

Endlich empfohlenes Muster

Für die meisten Single-Host-Linux-Services verwenden Sie dieses Muster:

Compose-Datei:

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

systemd-Einheit:

[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

Aktivieren Sie es:

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

Betreiben Sie es:

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

Dieses Muster ist nicht fancy, und das ist der Punkt. Docker Compose ist ausgezeichnet für kleine, verständliche Systeme, systemd ist ausgezeichnet darin, Host-Services zu starten und zu stoppen, und zusammen geben sie Ihnen ein zuverlässiges Single-Server-Deploymentsmodell, ohne so zu tun, als ob jedes Projekt ein Cluster benötigt. Für Container-Befehle außerhalb von Compose — Images, Volumes, Netzwerke und Bereinigung — sehen Sie das Docker Cheat-Sheet.

Abonnieren

Neue Beiträge zu Systemen, Infrastruktur und KI-Engineering.