Docker Compose als Linux-Dienst mit systemd ausführen
Docker Compose beim Systemstart, verwaltet durch systemd.
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.

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.
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
ExecStopaus, 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:
WorkingDirectoryzeigt auf das Compose-Projekt.ExecStartPrevalidiert die Compose-Konfiguration.ExecReloaderstellt geänderte Services neu.ExecStopstoppt 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 restartstartet existierende Container neu.docker compose up -dwendet 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.