Docker Grundlagen: Container, Images, Volumes und Netzwerke
Docker von Grund auf: Image und Container, Lebenszyklus, Volumes, Ports, Netzwerke und Umgebungsvariablen – mit getesteten Befehlen und einem Compose-Beispiel.
Von Niclas Hennrich · Veröffentlicht: · 12 Min. Lesezeit · Einsteiger
Getestet mit
- Docker Engine 29.9.0
- Docker Compose 5.6.0
- Debian 13
Technisch verifiziert am
Reality Check
Stand: Oktober 2026
Kurz gesagt: Docker startet Programme als Container: abgeschottete Prozesse, die aus einem unveränderlichen Image entstehen und jederzeit weggeworfen und neu erzeugt werden können. Damit dabei nichts verloren geht, liegen Daten in Volumes; über Port-Freigaben wird ein Dienst von außen erreichbar, über eigene Netzwerke finden sich Container gegenseitig per Namen. Wer diese vier Bausteine versteht, kann fast jede Homelab-Anleitung nachvollziehen.
Dieser Guide richtet sich an alle, die Docker bisher nur aus kopierten Befehlen kennen – oder noch gar nicht. Du arbeitest alles einmal auf der Kommandozeile durch, mit kleinen Wegwerf-Containern, die du am Ende wieder entfernst. Die Befehle und Ausgaben stammen aus einem Test mit Docker Engine 29.9.0 und Docker Compose 5.6.0 unter Debian 13; die Ausgaben sind gekürzt.
Voraussetzungen #
- Ein Linux-Host (VM, Mini-PC oder Testrechner) mit installiertem Docker Engine und Compose-Plugin. Die Installation über das offizielle apt-Repository zeigt der Guide Portainer installieren Schritt für Schritt – den Portainer-Teil kannst du dort vorerst überspringen.
- Ein Terminal mit Root-Rechten. Die Befehle unten sind ohne
sudogeschrieben; arbeitest du als normaler Benutzer, stellst dusudovoran. - Etwa 30 Minuten und kein Produktivsystem: Die Beispiele legen Container, ein Volume und ein Netzwerk an und räumen sie wieder ab.
Lernziel: Du kannst einen Container starten, prüfen und entfernen, Daten
dauerhaft in einem Volume ablegen, einen Port gezielt freigeben, zwei
Container über ein eigenes Netzwerk verbinden und dasselbe Setup als
compose.yaml beschreiben.
Die Bausteine: Image, Container, Registry und Tag #
Image #
Ein Image ist eine fertige Vorlage: ein Paket aus Dateisystem, Bibliotheken und einem Startbefehl für ein Programm. Es wird in Schichten (Layers) gebaut, und jede Schicht hält eine Änderung am Dateisystem fest. Wichtig für den Alltag: Ein Image wird nach dem Bauen nicht mehr verändert. Eine neue Version ist immer ein neues Image.
Container #
Ein Container ist ein laufender (oder gestoppter) Prozess, der aus einem Image gestartet wurde. Er bekommt eine eigene Sicht auf Dateisystem, Prozesse und Netzwerk, teilt sich aber den Kernel des Hosts – das unterscheidet ihn von einer virtuellen Maschine, die einen eigenen Kernel mitbringt. Aus einem Image kannst du beliebig viele Container starten.
Was ein Container während der Laufzeit in sein eigenes Dateisystem schreibt, landet in einer dünnen, beschreibbaren Schicht über dem Image. Diese Schicht verschwindet, wenn du den Container löschst. Daraus folgt die wichtigste Regel im Umgang mit Docker: Container sind Wegwerfware, Daten gehören in Volumes.
Registry und Tag #
Eine Registry ist ein Server, von dem Docker Images herunterlädt –
Standard ist Docker Hub. Der Tag hinter dem Doppelpunkt benennt eine
Variante des Images: nginx:1.30-alpine ist der Webserver nginx in der Versionslinie
1.30 (der aktuellen Stable-Linie) auf Basis des schlanken Alpine Linux. Fehlt der Tag, nimmt Docker
latest. Das klingt nach „neueste Version“, ist aber nur ein Name, den der
Herausgeber auf irgendeine Version zeigen lässt. Für Dienste, die dauerhaft
laufen, ist ein fester Tag die bessere Wahl.
Der erste Container in fünf Schritten #
1. Prüfen, ob Docker läuft. Der erste Befehl fragt den Daemon (den Hintergrunddienst, der die Container tatsächlich verwaltet) nach seiner Version, der zweite zeigt die Version des Compose-Plugins:
docker version --format '{{.Server.Version}}'
docker compose version29.9.0
Docker Compose version v5.6.0Kommt hier eine Fehlermeldung mit permission denied, fehlen dir die
Rechte am Docker-Socket – siehe Typische Probleme.
2. Image herunterladen. docker pull holt das Image aus der Registry,
docker image ls zeigt, was lokal vorhanden ist:
docker pull nginx:1.30-alpine
docker image ls nginx3. Container starten. Der folgende Befehl startet nginx im Hintergrund
(-d), gibt ihm den Namen web und macht Port 80 des Containers auf Port
8080 des Hosts erreichbar:
docker run -d --name web -p 8080:80 nginx:1.30-alpineDocker antwortet mit der langen ID des neuen Containers.
4. Prüfen, ob er läuft. docker ps listet laufende Container, curl
fragt die Startseite ab:
docker ps
curl -s http://localhost:8080 | head -4CONTAINER ID IMAGE STATUS PORTS NAMES
a1b2c3d4e5f6 nginx:1.30-alpine Up 5 seconds 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp web
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>Die Spalte PORTS zeigt schon etwas Wichtiges: Der Port ist auf allen
Adressen des Hosts freigegeben, IPv4 (0.0.0.0) wie IPv6 ([::]). Dazu
gleich mehr.
5. Logs ansehen. Alles, was das Programm im Container auf die
Standardausgabe schreibt, liest du mit docker logs:
docker logs --tail 3 webDarin taucht auch deine curl-Anfrage von eben als Zugriffszeile auf.
Der Lebenszyklus eines Containers #
Ein Container durchläuft immer dieselben Zustände: angelegt, laufend, beendet, gelöscht. Die passenden Befehle:
docker stop web # Prozess sauber beenden, Container bleibt erhalten
docker ps -a # zeigt auch beendete Container
docker start web # denselben Container wieder starten
docker restart web # stoppen und starten in einem Schritt
docker rm -f web # Container stoppen und löschenNach docker stop steht der Container in docker ps -a mit dem Status
Exited (0) – die Zahl in Klammern ist der Exit-Code des Programms, 0
bedeutet „ordentlich beendet“. Ein laufender Container lässt sich nicht mit
einem einfachen docker rm löschen; Docker verweigert das mit dem Hinweis
container is running: stop the container before removing or force remove.
Das -f erzwingt Stoppen und Löschen in einem Schritt. Weil damit die
beschreibbare Schicht des Containers verloren geht, nutze es nur für
Container, deren Daten in Volumes liegen oder die du ohnehin wegwerfen
willst.
Wenn ein Container sofort wieder aus ist #
Ein Container läuft genau so lange wie sein Hauptprozess. Beendet sich der Prozess wegen eines Fehlers, ist auch der Container beendet. Das lässt sich gefahrlos nachstellen:
docker run --name kaputt alpine:3.22 sh -c 'echo "Konfiguration fehlt" >&2; exit 3'
docker ps -a --filter name=kaputt
docker logs kaputt
docker inspect -f '{{.State.ExitCode}}' kaputt
docker rm kaputtDer Status lautet Exited (3), docker logs zeigt die Fehlermeldung des
Programms, und docker inspect liefert den Exit-Code 3. Diese Reihenfolge
– Status, Logs, Exit-Code – ist bei jedem Container, der nicht startet, der
erste Diagnoseschritt.
Neustart-Richtlinien #
Ob Docker einen Container nach einem Absturz oder einem Neustart des Hosts wieder hochfährt, legt die Restart-Policy fest:
| Policy | Verhalten |
|---|---|
no | Standard: kein automatischer Neustart |
on-failure | Neustart nur, wenn das Programm mit einem Fehlercode endet |
always | Neustart in jedem Fall; auch ein von Hand gestoppter Container läuft nach einem Neustart des Docker-Dienstes wieder |
unless-stopped | wie always, aber ein von dir gestoppter Container bleibt nach einem Neustart des Docker-Dienstes aus |
Für typische Homelab-Dienste passt unless-stopped: Der Dienst überlebt
Abstürze und Reboots, respektiert aber ein bewusstes docker stop.
docker run -d --name web --restart unless-stopped -p 8080:80 nginx:1.30-alpine
docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' webDie Ausgabe ist unless-stopped. Räum den Testcontainer danach wieder ab,
sonst ist Port 8080 im Compose-Beispiel unten belegt:
docker rm -f webEine Restart-Policy repariert aber keinen
Fehler: Scheitert das Programm bei jedem Start, siehst du in docker ps -a
den Status Restarting (1), und der Zähler RestartCount in
docker inspect steigt immer weiter. Dann helfen nur die Logs.
Daten behalten: Volumes und Bind Mounts #
Es gibt zwei übliche Wege, Daten außerhalb des Containers abzulegen:
- Ein Volume ist ein von Docker verwalteter Speicherbereich. Du sprichst
es über seinen Namen an, Docker legt die Daten auf Linux unter
/var/lib/docker/volumes/ab. - Ein Bind Mount blendet ein bestimmtes Verzeichnis des Hosts in den
Container ein, etwa
/opt/app/config. Praktisch, wenn du die Dateien auch vom Host aus bearbeiten willst.
Dass ein Volume den Container überlebt, zeigt ein kurzer Versuch. Der
Schalter --rm sorgt dafür, dass Docker den Container nach dem Ende
automatisch löscht:
docker volume create web-daten
docker run --rm -v web-daten:/daten alpine:3.22 sh -c 'echo hallo > /daten/test.txt'
docker run --rm -v web-daten:/daten alpine:3.22 cat /daten/test.txt
docker run --rm alpine:3.22 cat /daten/test.txthallo
cat: can't open '/daten/test.txt': No such file or directoryDer erste Container schreibt die Datei und ist danach weg. Ein zweiter,
völlig neuer Container mit demselben Volume liest sie problemlos. Der dritte
Container hat kein Volume eingebunden – für ihn existiert die Datei nicht.
Wo das Volume auf dem Host liegt, verrät
docker volume inspect web-daten --format '{{.Mountpoint}}'. Das
Test-Volume löschst du mit docker volume rm web-daten – der Befehl
entfernt die Daten darin endgültig, bei echten Volumes also nur mit Backup.
Bei Bind Mounts kommt eine Stolperfalle dazu: Die Dateirechte des
Host-Verzeichnisses gelten auch im Container. Läuft das Programm im
Container als Benutzer mit der ID 1000 und gehört das Verzeichnis root,
scheitert jeder Schreibversuch mit Permission denied. Die Lösung ist,
den Besitzer des Verzeichnisses passend zu setzen (siehe
Typische Probleme).
Ports: wie ein Dienst erreichbar wird #
Ein Container hat seine eigene Netzwerkschnittstelle. Von außerhalb des
Hosts ist er erst erreichbar, wenn du einen Port veröffentlichst. Die
Angabe -p 8080:80 liest sich immer als HOST:CONTAINER: Anfragen an Port
8080 des Hosts landen auf Port 80 im Container. So können zwei Container
intern beide Port 80 nutzen und trotzdem nebeneinander laufen – sie bekommen
einfach unterschiedliche Host-Ports.
Ohne weitere Angabe gibt Docker den Port auf allen Adressen des Hosts frei. Soll ein Dienst nur lokal oder nur in einem bestimmten Netz erreichbar sein, setzt du die Adresse davor:
docker run -d --name intern -p 127.0.0.1:8081:80 nginx:1.30-alpine
docker run -d --name lan -p 192.168.10.20:8082:80 nginx:1.30-alpineDer erste Container ist nur vom Host selbst aus erreichbar, der zweite
lauscht nur auf der LAN-Adresse 192.168.10.20 (ein Beispielwert – setze die
Adresse deines Hosts ein). Die Adresse legt fest, auf welcher Schnittstelle
der Port offen ist, nicht wer darauf zugreifen darf: Laut Docker-Doku
erreichen auch Geräte aus anderen Netzen den Port, wenn ihr Verkehr zu dieser
Adresse geroutet wird. Eine Firewall ersetzt die Bindung also nicht. Die
beiden Testcontainer entfernst du mit docker rm -f intern lan wieder.
Netzwerke: wie Container sich finden #
Container, die zusammenarbeiten – etwa eine Anwendung und ihre Datenbank –, müssen sich gegenseitig erreichen. Dafür gibt es Docker-Netzwerke. Der entscheidende Unterschied:
- Im vorgegebenen Netzwerk
bridge, in dem jeder Container ohne weitere Angabe landet, erreichen sich Container nur über ihre wechselnden IP-Adressen. - In einem selbst angelegten Netzwerk betreibt Docker einen eigenen DNS-Dienst. Container finden sich dort über ihren Namen.
So siehst du den Unterschied direkt:
docker network create labnetz
docker run -d --name app --network labnetz traefik/whoami:v1.11
docker run --rm --network labnetz alpine:3.22 wget -qO- http://app:80
docker run --rm alpine:3.22 wget -qO- -T 3 http://app:80Hostname: 9f8e7d6c5b4a
IP: 127.0.0.1
...
wget: bad address 'app:80'traefik/whoami ist ein winziger Testdienst, der auf jede Anfrage mit
Informationen über sich selbst antwortet. Der Container im Netzwerk
labnetz erreicht ihn über den Namen app; der Container im
Standardnetz kann den Namen nicht auflösen. Merke dir dieses Fehlerbild –
bad address heißt fast immer: Die beiden Container hängen nicht im selben
selbst angelegten Netzwerk. Beachte auch: app hat keinen veröffentlichten
Port und ist trotzdem für Container im selben Netzwerk erreichbar. Eine
Datenbank, die nur deine Anwendung braucht, sollte genau so laufen – ohne
-p.
Aufräumen:
docker rm -f app
docker network rm labnetzUmgebungsvariablen: Einstellungen ohne neues Image #
Viele Images lesen ihre Konfiguration aus Umgebungsvariablen – benannten
Werten, die beim Start an den Prozess übergeben werden. Mit -e setzt du
sie:
docker run --rm -e GRUSS=moin alpine:3.22 sh -c 'echo $GRUSS'Die Ausgabe ist moin. Welche Variablen ein Image versteht, steht in
seiner Dokumentation, meist auf der Docker-Hub-Seite. Für Passwörter und
Schlüssel sind Umgebungsvariablen nur die zweitbeste Lösung: Jeder, der den
Container per docker inspect ansehen darf, sieht sie im Klartext. Die
Docker-Dokumentation empfiehlt für sensible Werte Secrets; im Homelab ist
mindestens eine separate .env-Datei mit eingeschränkten Rechten sinnvoll,
die nicht in ein Git-Repository wandert. Sie hält Passwörter aus der
compose.yaml heraus; per docker inspect bleiben sie im laufenden
Container aber sichtbar.
Von docker run zu Docker Compose #
Lange docker run-Zeilen sind schwer zu pflegen. Docker Compose
beschreibt dasselbe in einer YAML-Datei namens compose.yaml: welche
Dienste (Services) es gibt, welches Image sie nutzen, welche Ports, Volumes
und Variablen sie bekommen. Das folgende Beispiel kombiniert alles aus
diesem Guide – einen Webserver mit eingebundenem Verzeichnis und den
whoami-Testdienst mit einer Umgebungsvariable.
Verzeichnis und Startseite anlegen:
mkdir -p ~/docker-demo/html
cd ~/docker-demo
echo '<h1>Hallo aus dem Bind Mount</h1>' > html/index.html
nano compose.yamlservices:
web:
image: nginx:1.30-alpine
restart: unless-stopped
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
app:
image: traefik/whoami:v1.11
restart: unless-stopped
environment:
WHOAMI_NAME: "labkompass"Der Zusatz :ro bindet das Verzeichnis nur lesend ein – nginx soll die
Dateien ausliefern, aber nicht verändern. Starten und prüfen:
docker compose up -d
docker compose ps
curl -s http://localhost:8080
docker compose exec web wget -qO- http://app:80 | grep NameNAME IMAGE SERVICE STATUS PORTS
docker-demo-app-1 traefik/whoami:v1.11 app Up 2 seconds 80/tcp
docker-demo-web-1 nginx:1.30-alpine web Up 2 seconds 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp
<h1>Hallo aus dem Bind Mount</h1>
Name: labkompassDrei Dinge erledigt Compose dabei von selbst: Es benennt die Container nach
dem Verzeichnis (dem Projektnamen), legt für das Projekt ein eigenes
Netzwerk docker-demo_default an, und die Dienste finden sich darin über
ihren Service-Namen – web erreicht app ohne veröffentlichten Port. Die
Variable WHOAMI_NAME ist im Container angekommen, wie die letzte Zeile
zeigt.
Logs aller Dienste siehst du mit docker compose logs, gestoppt und
entfernt wird das ganze Projekt mit:
docker compose downdown löscht Container und Projektnetzwerk, lässt aber das Verzeichnis
html und benannte Volumes in Ruhe. Erst docker compose down -v würde
auch die benannten Volumes des Projekts löschen – mit allen Daten darin.
Typische Probleme #
Bind for 0.0.0.0:8080 failed: port is already allocated– Ursache: Ein anderer Container oder Dienst nutzt den Host-Port. Lösung: Mitss -tlnp | grep 8080nachsehen, wer ihn belegt (bei einem Container steht dortdocker-proxy), und einen anderen Host-Port wählen.pull access denied … repository does not exist– Ursache: Tippfehler im Image-Namen.failed to resolve reference … not found– Ursache: Der Tag existiert nicht. Lösung in beiden Fällen: Namen und Tag auf Docker Hub prüfen.permission denied while trying to connect to the docker API– Ursache: Dein Benutzer darf den Docker-Socket nicht ansprechen. Lösung:sudovoranstellen oder den Benutzer bewusst in die Gruppedockeraufnehmen (Sicherheitshinweis unten beachten).- Container steht auf
Exited (…)– Ursache: Der Hauptprozess hat sich beendet. Lösung:docker logs <name>lesen; die Fehlermeldung des Programms steht fast immer dort. - Status
Restarting– Ursache: Das Programm scheitert bei jedem Start, die Restart-Policy startet es immer wieder. Lösung: Logs lesen, Fehler beheben, Container neu erzeugen. bad addressbeim Aufruf eines anderen Containers – Ursache: Die Container hängen nicht im selben selbst angelegten Netzwerk. Lösung: gemeinsames Netzwerk anlegen oder beide Dienste in dieselbecompose.yamlschreiben.Permission deniedbeim Schreiben in einen Bind Mount – Ursache: Das Host-Verzeichnis gehört einem anderen Benutzer als dem, unter dem das Programm im Container läuft. Lösung: Besitzer anpassen, z. B.chown 1000:1000 /opt/app/data, passend zur Benutzer-ID aus der Doku des Images.did not find expected keyausdocker compose config– Ursache: Einrückungsfehler in der YAML-Datei. Lösung: nur Leerzeichen verwenden, konsequent zwei pro Ebene, und die Datei vor dem Start mitdocker compose configprüfen lassen.
Sicherheit und Betrieb #
- Docker-Rechte sind Root-Rechte. Wer Docker steuern darf, kann einen
Container mit dem kompletten Host-Dateisystem starten. Die
Docker-Dokumentation warnt ausdrücklich, dass die Gruppe
dockerRechte auf Root-Niveau verleiht. Nimm nur Benutzer auf, denen du auch Root anvertrauen würdest. - Der Docker-Socket ist der Generalschlüssel. Über
/var/run/docker.sockwird der Docker-Dienst gesteuert. Ein Container, dem du den Socket einbindest, hat damit dieselbe Macht über den Host. Das betrifft vor allem Verwaltungswerkzeuge wie Portainer – Details und Gegenmaßnahmen im Guide Docker und Portainer absichern. - Veröffentlichte Ports umgehen ufw und firewalld. Die
Installationsanleitung von Docker weist darauf hin: Ports, die Docker
veröffentlicht, laufen an den Regeln dieser Firewalls vorbei. Gib Ports
deshalb gezielt auf
127.0.0.1oder einer LAN-Adresse frei und veröffentliche nur, was wirklich von außen erreichbar sein muss. Die Bindung begrenzt die Adresse, nicht die Absender – den Zugriff selbst regelt weiterhin eine Firewall, etwa am Router. - Feste Versionen statt
latest. Dann ist ein Update eine bewusste Änderung an dercompose.yamlund kein Zufall beim nächsten Pull. - Backups betreffen Volumes, nicht Container. Container lassen sich
jederzeit aus Image und
compose.yamlneu erzeugen. Sichern musst du die Compose-Dateien und die Volumes beziehungsweise Bind-Mount-Verzeichnisse. Befehle wiedocker volume rmoderdocker volume prunelöschen Daten endgültig – probiere sie nur in einer Testumgebung aus und erst, wenn ein Backup existiert. Wie eine solche Sicherung zuhause aussehen kann, beschreibt die Backup-Strategie für zuhause.
Weiterlesen #
- Docker in Proxmox: VM oder LXC-Container? klärt, wo dein Docker-Host im Homelab am besten läuft.
- Mit dem Docker-Run-Command-Builder
baust du
docker run-Befehle mit Ports, Volumes und Variablen, ohne dir jede Option merken zu müssen; der Docker-Compose-Generator erzeugt daraus einecompose.yaml. - Was ist Portainer? zeigt, wie eine Weboberfläche genau diese Bausteine – Container, Images, Volumes, Netzwerke – sichtbar macht, und wann die Kommandozeile trotzdem die bessere Wahl bleibt.
- Die komplette Portainer-Serie baut auf diesem Guide auf: von der Installation über Stacks bis zu Backup und Fehlersuche.
Quellen
Offizielle Dokumentation
Docker Docs — What is a container? (externer Link, neues Fenster)
Offizielle Dokumentation
Offizielle Dokumentation
Docker Docs — Networking overview (externer Link, neues Fenster)
Offizielle Dokumentation
Docker Docs — Port publishing and mapping (externer Link, neues Fenster)
Offizielle Dokumentation
Docker Docs — Start containers automatically (externer Link, neues Fenster)
Offizielle Dokumentation
Offizielle Dokumentation
Docker Docs — Install Docker Engine on Debian (externer Link, neues Fenster)
Offizielle Dokumentation
Docker Docs — Linux post-installation steps for Docker Engine (externer Link, neues Fenster)
Häufige Fragen
- Ist ein Docker-Container dasselbe wie eine virtuelle Maschine?
- Nein. Eine VM bringt ein komplettes Betriebssystem mit eigenem Kernel mit, ein Container ist dagegen ein abgeschotteter Prozess, der den Kernel des Hosts mitbenutzt. Dadurch starten Container in Sekunden und brauchen wenig Overhead, die Trennung zum Host ist aber weniger hart als bei einer VM. Im Homelab kombiniert man oft beides: Docker läuft in einer VM.
- Verliere ich meine Daten, wenn ich einen Container lösche?
- Alles, was der Container nur in sein eigenes Dateisystem geschrieben hat, ist mit docker rm weg. Daten in einem Volume oder Bind Mount bleiben dagegen erhalten und stehen dem nächsten Container wieder zur Verfügung. Deshalb gehört jedes Verzeichnis, das ein Update überleben soll, in ein Volume – und genau diese Volumes gehören ins Backup.
- Brauche ich Docker Compose oder reicht docker run?
- Für einen schnellen Test reicht docker run. Sobald ein Dienst dauerhaft laufen soll oder aus mehreren Containern besteht, ist Compose die bessere Wahl: Die compose.yaml hält Image-Version, Ports, Volumes und Variablen in einer Datei fest, die du versionieren und auf einem anderen Host identisch wieder starten kannst.
- Warum ist latest als Image-Tag keine gute Idee?
- latest ist nur ein Name, keine Versionsangabe. Er kann morgen auf eine ganz andere Version zeigen als heute, und ein docker compose pull holt dann unbemerkt eine neue Hauptversion. Mit einem Versions-Tag wie nginx:1.30-alpine bleibst du auf einer Versionslinie und entscheidest selbst, wann du auf die nächste wechselst.
Teil des Themen-Hubs Docker & Compose im Homelab · Abschnitt „Grundlagen“
Weiterlesen
Portainer
Was ist Portainer? Docker per Weboberfläche verwalten
Portainer erklärt: was die Docker-Weboberfläche leistet, wie sie zu Docker und Compose steht, CE oder Business Edition – und wann die Kommandozeile besser ist.
Docker
Docker in Proxmox: VM oder LXC-Container?
Docker unter Proxmox läuft in einer VM oder im LXC — aber nur einer der beiden Wege ist offiziell empfohlen. Die Architektur erklärt, beide Wege konkret gezeigt, mit ehrlicher Entscheidungshilfe.
Netzwerk
Pi-hole mit Docker Compose betreiben
Pi-hole als Container statt als Systeminstallation: eine nachvollziehbare compose.yaml, der Umgang mit belegtem Port 80, ein Speicherlimit für kleine Geräte und die Frage, welchen Upstream-DNS du eintragen solltest.