Managed Service Podman
Podman ist eine daemonlose Container-Engine zum Entwickeln, Verwalten und Ausführen von OCI-Containern unter Linux. Auf unseren Managed Servern kannst du Container rootless betreiben.
Da die Syntax in vielen Fällen Docker ähnelt, kannst du dir mit folgendem Alias die Arbeit erleichtern:
alias docker=podman
Beachte aber, dass Podman nicht in jedem Fall ein vollständiger Docker-Ersatz ist. Je nach Podman-Version können sich Syntax und Verhalten unterscheiden.
Die vollständige offizielle Dokumentation findest du hier: https://podman.readthedocs.io/en/latest/index.html
Cleanup-Jobs
Auf unserem Managed Podman Service richten wir standardmässig zwei Cleanup-Jobs ein:
- Einer läuft in der Nacht von Sonntag auf Montag und entfernt ungenutzte Images.
- Einer läuft in der Nacht vor dem ersten Tag jedes Monats und entfernt ungenutzte Volumes.
Diese Jobs helfen dabei, den Speicherverbrauch von Podman zu begrenzen und das Risiko zu senken, dass der Speicherplatz knapp wird.
Wenn du diese Cleanup-Jobs anpassen möchtest, erstelle ein Support-Ticket. Wir passen sie dann an deine Anforderungen an.
Images suchen, holen und anzeigen
Suche in Remote-Registries nach Images:
podman search <search_term>
Filtere die Suchergebnisse:
podman search ghost --filter=is-official
Hole ein Image lokal auf den Server:
podman pull docker.io/library/ghost
Zeige lokal vorhandene Images an:
podman images
Podman kann in mehreren Registries suchen. Wir empfehlen, den voll qualifizierten Image-Namen zu verwenden, also zum Beispiel docker.io/library/ghost statt ghost, damit du sicher das erwartete Image verwendest.
Container starten
Das folgende Beispiel startet einen Ghost-Container für eine einfache Website. Er läuft im Entwicklungsmodus (-e NODE_ENV=development), damit Ghost die eingebaute SQLite-Datenbank verwendet und keine externe MySQL-Datenbank benötigt.
podman run --detach=true --tty --name ghost-cms -p 8080:2368/tcp -e NODE_ENV=development docker.io/library/ghost
Mit -d oder --detach=true startet der Container im Hintergrund. Nach dem Start gibt Podman die Container-ID aus.
Mit -t oder --tty wird ein Pseudo-TTY zugewiesen. Das ist vor allem für interaktive Anwendungsfälle nützlich.
Mit -p 8080:2368/tcp wird Ghosts Webserver auf TCP-Port 2368 im Container als TCP-Port 8080 auf dem Host veröffentlicht.
Wenn du später mit Podlet ein Quadlet erzeugen möchtest, verwende --detach=true und --tty statt -d und -t. Podlet wertet den gespeicherten podman run-Befehl striker aus als Podman selbst.
Laufende Container anzeigen
Mit podman ps -a zeigst du erstellte und laufende Container an:
$ podman ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5728ad900bc4 docker.io/library/ghost:latest node current/inde... 4 hours ago Up 4 hours ago 0.0.0.0:8080->2368/tcp gifted_edison
Mit einem laufenden Container verbinden
Mit der CONTAINER ID aus podman ps kannst du dich mit einem laufenden Container verbinden:
$ podman attach b3376ff455a0
[2020-06-10 09:17:15] INFO "GET /" 200 512ms
Container testen
Der Container ist jetzt auf Port 8080 auf dem Host erreichbar.
Mit curl kannst du prüfen, ob Ghost korrekt antwortet:
$ curl -s localhost:8080 | grep "og:site"
<meta property="og:site_name" content="Ghost" />
Anwendung mit nine-manage-vhosts veröffentlichen
Wenn du bereits Managed Nginx oder Apache2 verwendest, kannst du deine Container-Anwendung mit nine-manage-vhosts veröffentlichen und TLS mit Let's Encrypt terminieren.
Erstelle den Virtual Host:
sudo nine-manage-vhosts virtual-host create testdomain.org --template=proxy --template-variable=PROXYPORT=8080
Registriere den Let's-Encrypt-Client:
sudo nine-manage-vhosts certificate register-client
Erstelle das Zertifikat und stelle den vHost auf das HTTPS-Proxy-Template um:
sudo nine-manage-vhosts certificate create --virtual-host=testdomain.org
sudo nine-manage-vhosts virtual-host update testdomain.org --template=proxy_letsencrypt_https --template-variable=PROXYPORT=8080
Wenn du Apache verwendest und automatische HTTP-zu-HTTPS-Weiterleitungen möchtest, verwende das Template proxy_letsencrypt_https_redirect.
Compose-Dateien
Podman kann Compose-Dateien ausführen. Welcher Befehl verwendet wird, hängt von der Ubuntu-Version ab.
- Ubuntu Noble 24.04 & Resolute 26.04
- Ubuntu Jammy 22.04
Auf Ubuntu Noble und Resolute verwendest du für Compose-Dateien standardmässig podman compose. Falls du noch Legacy-Workflows mit Docker-kompatiblen Befehlen hast, steht docker compose als Fallback zur Verfügung, wenn podman-docker installiert ist.
podman compose -f docker-compose.yaml up
Wenn podman-docker auf dem Server installiert ist, funktioniert als Fallback auch Folgendes:
docker compose -f docker-compose.yaml up
Ubuntu 22.04 bringt docker-compose v1 mit. Dieser Befehl verwendet einen Bindestrich:
docker-compose -f docker-compose.yaml up
Installiere die neueste v2 Version von docker-compose
mkdir -p ~/bin
curl -L $(curl -s https://api.github.com/repos/docker/compose/releases/latest | jq -r '.assets[] | select(.name == "docker-compose-linux-x86_64") | .browser_download_url') -o ~/bin/docker-compose
chmod +x ~/bin/docker-compose
Beispiel für eine Compose-Datei
Das folgende Beispiel startet Ghost mit einer Compose-Datei. Es nutzt das standardmässige rootless-Netzwerk-Backend, also pasta auf Resolute und slirp4netns auf Jammy und Noble. Deshalb ist kein expliziter Netzwerkmodus in der Compose-Datei nötig.
Dieses Beispiel verbindet Ghost mit einer bereits auf dem Host laufenden MySQL-Datenbank namens nmd_ghost. Um eine Datenbank auf dem Host aus einem rootless Container zu erreichen, verwende host.containers.internal statt localhost. Falls die Datenbank nur auf dem Host-Loopback lauscht, muss das Netzwerk-Backend passend konfiguriert sein. Details dazu findest du unter Verbindung zu einer Datenbank auf dem Host.
Erstelle deine docker-compose.yaml-Datei:
# Dieses Beispiel konfiguriert Ghost für eine lokale MySQL-Datenbank auf dem Host.
services:
ghost:
image: docker.io/library/ghost
restart: always
ports:
- 8080:2368
environment:
# see https://docs.ghost.org/docs/config#section-running-ghost-with-config-env-variables
database__client: mysql
database__connection__host: host.containers.internal
database__connection__user: nmd_ghost
database__connection__password: EeNae5xaoapoh5RoDah1muwu
database__connection__database: nmd_ghost
# this url value is only an example and is likely wrong for your environment
url: http://testdomain.org
Starte die Compose-Datei wie folgt:
- Ubuntu Noble 24.04 & Resolute 26.04
- Ubuntu Jammy 22.04
podman compose -f docker-compose.yaml up
docker-compose -f docker-compose.yaml up
Füge -d hinzu, wenn du den Compose-Stack im Hintergrund starten möchtest.
Container automatisch mit systemd starten
Wir empfehlen, einen systemd-User-Service zu erstellen, damit der Container nach einem Neustart automatisch startet. Das genaue Vorgehen hängt von der OS-Version ab.
Obwohl neuere Podman-Versionen weiterhin podman generate systemd unterstützen, empfehlen wir auf Ubuntu Noble und Resolute Quadlets, weil sie klarere und leichter kontrollierbare systemd-Units erzeugen.
- Ubuntu Noble 24.04 & Resolute 26.04
- Ubuntu Jammy 22.04
Quadlet ist ein Format für von Podman verwaltete systemd-Units und ist in Podman 4.4 und neuer integriert. Eine Quadlet-Datei endet auf .container, .image, .volume, .network oder .kube und liegt bei rootless Services typischerweise in ~/.config/containers/systemd/.
Quadlet-Dateien verwenden dieselbe Grundstruktur wie normale systemd-Unit-Dateien. Standard-Sektionen wie [Service] und [Install] werden direkt an systemd weitergegeben, während Podman-spezifische Sektionen wie [Container] beschreiben, wie der spätere podman run-Befehl erzeugt wird.
Details dazu findest du in der Podman-Quadlet-Dokumentation.
Wenn Podlet auf deinem Server verfügbar ist, kannst du damit aus einem bestehenden Container eine sofort nutzbare Quadlet-Datei erzeugen.
Erstelle den Container mit einem festen Namen und allen Umgebungsvariablen, Volume-Mounts und veröffentlichten Ports, die Podlet übernehmen soll:
podman run --detach=true --tty --name ghost-cms \
-e NODE_ENV=development \
-e database__connection__filename=/var/lib/ghost/content/data/ghost.db \
-v some-ghost-data:/home/www-data/ghost \
-p 8080:2368/tcp \
docker.io/library/ghost
Erzeuge danach die Quadlet-Datei:
podlet --unit-directory --install --overwrite generate container ghost-cms
Wrote to file: /home/www-data/.config/containers/systemd/ghost-cms.container
Mit podman ps und podman pod ps kannst du Container- und Pod-Namen anzeigen.
Die Quadlet-Datei wird nach ~/.config/containers/systemd/ geschrieben:
cat ~/.config/containers/systemd/ghost-cms.container
[Container]
Environment=NODE_ENV=development database__connection__filename=/var/lib/ghost/content/data/ghost.db
Image=docker.io/library/ghost
PodmanArgs=--tty
PublishPort=8080:2368/tcp
Volume=some-ghost-data:/home/www-data/ghost
[Install]
WantedBy=default.target
Passe die Datei bei Bedarf an. Lade danach die systemd-User-Instanz neu:
systemctl --user daemon-reload
systemctl --user daemon-reload erzeugt keine separate statische Unit-Datei. Quadlet-Dateien werden von einem systemd-Generator in generierte Units umgewandelt.
Weil diese generierten Services transient sind, lassen sie sich nicht mit systemctl --user enable aktivieren. Stattdessen übernimmt systemd die unterstützten Schlüssel aus der [Install]-Sektion der Quadlet-Datei während der Generierung. Deshalb sorgt WantedBy=default.target trotzdem für einen automatischen Start nach dem Reboot.
Starte den Service:
systemctl --user start ghost-cms.service
systemctl --user status ghost-cms.service
● ghost-cms.service
Loaded: loaded (/home/www-data/.config/containers/systemd/ghost-cms.container; generated)
Active: active (running) since Wed 2024-12-04 13:39:10 CET; 1s ago
Main PID: 540254 (conmon)
Tasks: 25 (limit: 3337)
Memory: 80.8M (peak: 80.8M)
CPU: 1.100s
CGroup: /user.slice/user-33.slice/user@33.service/app.slice/ghost-cms.service
├─libpod-payload-805ff2ecbcaa737f9b969dd21323b0115fb3c46c7800ab88563663a1e41341b8
│ └─540256 node current/index.js
└─runtime
└─540254 /usr/bin/conmon --api-version 1 -c 805ff2ecbcaa737f9b969dd21323b0115fb3c46c7800ab88563663a1e413>
Podlet kann Quadlets auch aus Podman-Kommandos oder Compose-Dateien erzeugen.
Die vollständige Dokumentation findest du im Podlet README.
Erzeuge eine systemd-User-Unit für den Pod mit dem Namen app:
mkdir -p ~/.config/systemd/user/
podman generate systemd --new --name app > ~/.config/systemd/user/app.service
Wenn der Container oder Pod bereits existiert, zum Beispiel weil er mit Compose erstellt wurde, lasse --new weg.
Mit podman ps und podman pod ps kannst du Container- und Pod-Namen anzeigen.
Lade anschliessend systemd neu und aktiviere den Service:
systemctl --user daemon-reload
systemctl --user enable app.service
Ressourcen begrenzen
Du kannst Container-Ressourcen entweder mit Podman-Optionen wie --cpus oder --memory oder mit systemd-Einstellungen wie CPUWeight oder MemoryMax begrenzen. Mehr dazu findest du in unserer Dokumentation zu Ressourcenlimitierungen.
Netzwerk
Modi (pasta, slirp4netns, host)
Welches rootless-Netzwerk-Backend standardmässig verwendet wird, hängt von der Podman-Version ab. Seit Podman 5.0 ist pasta der Standard, davor slirp4netns.
Auf unseren Managed Servern verwendet Ubuntu Resolute (Podman 5.7) deshalb standardmässig pasta, während Ubuntu Jammy (Podman 3.4.4) und Ubuntu Noble (Podman 4.9.3) standardmässig slirp4netns verwenden.
Container im selben Podman-Pod teilen sich denselben Netzwerk-Namespace. Sie teilen sich also dieselbe IP-Adresse, MAC-Adresse und dieselben veröffentlichten Ports und erreichen sich gegenseitig immer über localhost.
Du kannst auch --network=host verwenden. Im Host-Modus teilt der Container den Netzwerk-Namespace des Hosts und erhält keine eigene IP-Adresse. Das ist der einfachste Weg, um Dienste auf dem Host zu erreichen, hebt aber die Netzwerkisolation auf.
Verbindung zu einer Datenbank auf dem Host
Innerhalb eines rootless Containers beziehen sich localhost und 127.0.0.1 auf den Container selbst und nicht auf den Host. Ein Fehler wie connect ECONNREFUSED 127.0.0.1:3306 bedeutet meist, dass die Anwendung versucht, eine Datenbank auf ihrem eigenen Loopback zu erreichen.
Um eine Datenbank auf dem Host zu erreichen, verwende host.containers.internal. Podman trägt diesen Hostnamen in der Regel automatisch in die /etc/hosts des Containers ein. Die Details hängen vom Netzwerk-Backend ab:
- Ubuntu Resolute 26.04
- Ubuntu Jammy 22.04 & Noble 24.04
Resolute verwendet standardmässig pasta. In der Standardkonfiguration von Podman wird host.containers.internal zu 169.254.1.2 aufgelöst, weil Podman --map-guest-addr 169.254.1.2 an pasta übergibt.
In den meisten Fällen reicht es deshalb, einfach host.containers.internal zu verwenden:
podman run -dt -p 8080:2368/tcp \
-e database__connection__host=host.containers.internal \
docker.io/library/ghost
Wenn du die pasta-Standardwerte in ~/.config/containers/containers.conf überschreibst, stelle sicher, dass weiterhin --map-guest-addr 169.254.1.2 gesetzt wird, zum Beispiel so:
[network]
pasta_options = ["--map-guest-addr", "169.254.1.2"]
Jammy und Noble verwenden standardmässig slirp4netns. Wenn die Datenbank nur auf dem Host-Loopback lauscht, aktiviere allow_host_loopback und verbinde dich mit host.containers.internal oder 10.0.2.2:
podman run -dt -p 8080:2368/tcp \
--network slirp4netns:allow_host_loopback=true \
-e database__connection__host=host.containers.internal \
docker.io/library/ghost
Alternativ kannst du --network=host verwenden, sodass localhost:3306 im Container direkt auf die Datenbank des Hosts zeigt. Das ist einfacher, hebt aber die Netzwerkisolation auf.
Rootless-Modus
Unsere Container laufen rootless. Podman erstellt die rootless-Netzwerkkonfiguration automatisch.
Ports veröffentlichen
Rootless Container können auf dem Host nur unprivilegierte Ports veröffentlichen. Ports unter 1024 sind privilegiert und lassen sich deshalb nicht direkt durch einen rootless Container veröffentlichen.
Die Anwendung kann im Container trotzdem auf jedem beliebigen Port lauschen. Entscheidend ist nur, dass auf dem Host ein unprivilegierter Port verwendet wird. In diesem Beispiel lauscht Ghost im Container auf Port 2368 und wird auf dem Host auf Port 8080 veröffentlicht:
podman run -dt -p 127.0.0.1:8080:2368/tcp -e NODE_ENV=development docker.io/library/ghost
Mit -P kann Podman automatisch einen freien Host-Port zuweisen.
Prüfe die veröffentlichten Ports:
$ podman port -a
c0194f22266c 2368/tcp -> 127.0.0.1:8080
Kommunikation zwischen Containern
Es gibt mehrere Wege, wie rootless Container miteinander kommunizieren können.
Wenn beide Container im selben Podman-Pod laufen, erreichen sie sich direkt über localhost.
Wenn sie getrennt laufen, ist der einfachste Weg oft die Kommunikation über auf dem Host veröffentlichte Ports:
podman ps
Zeige die veröffentlichten Ports und die Host-IP an:
podman port <container_id>
ip a
Starte einen weiteren Container und greife über die Host-IP auf den veröffentlichten Port zu:
podman run --rm docker.io/curlimages/curl -s <HOST_IP_ADDRESS>:8080
Volumes: Volumes und Verzeichnisse in einen Container mounten
Um Daten zu persistieren, musst du sie entweder in einem externen Dienst wie einer Datenbank speichern oder mit dem -v-Flag (volume) ein Volume oder Verzeichnis in den Container mounten.
Die beiden wichtigsten Optionen für persistente Daten sind:
- Bind Mounts
- Named Volumes
Beide bleiben erhalten, wenn du einen Container entfernst und neu erstellst. Als Faustregel gilt: Verwende ein Named Volume, wenn Podman den Speicherort für dich verwalten soll. Verwende einen Bind Mount, wenn der Container einen ganz bestimmten Pfad vom Host benötigt.
Podman unterstützt auch nicht persistente Mounts wie tmpfs Mounts im Speicher und Overlay Mounts mit der Option :O, deren Änderungen beim Stoppen des Containers verworfen werden.
Bind Mounts
Ein Bind Mount bindet einen bestehenden Pfad vom Host in den Container ein, zum Beispiel -v /home/user/config:/etc/app/config. Das ist besonders für Konfigurationsdateien oder Inhalte sinnvoll, die du auch direkt auf dem Host bearbeiten möchtest.
Da die Container rootless laufen, werden Container-UIDs und -GIDs auf untergeordnete IDs (subuid und subgid) des Hosts abgebildet. Dateien, die über einen Bind Mount geschrieben werden, können deshalb einer abgebildeten UID gehören, die dein normaler Benutzer nicht direkt entfernen kann. Entferne sie so:
podman unshare rm -r ${bind_mount_dir}
Named Volumes
Named Volumes werden von Podman verwaltet und können über die Podman-CLI angezeigt, inspiziert und gelöscht werden, zum Beispiel mit podman volume ls, podman volume inspect und podman volume rm.
Sie liegen unter ~/.local/share/containers/storage/volumes/. Weil Podman sowohl Speicherort als auch Lebenszyklus verwaltet, sind Named Volumes die empfohlene Standardwahl für Anwendungsdaten wie Datenbanken.
Mount-Optionen
Optionen werden nach einem Doppelpunkt angehängt, zum Beispiel -v some-data:/data:ro. Die nützlichsten Optionen auf einem Managed Server sind:
:romountet das Volume schreibgeschützt. Das ist nützlich für Konfigurationen oder gemeinsam genutzte Referenzdaten.:Uändert rekursiv den Besitzer der Quelle auf die UID und GID des Container-Prozesses. Das kann Berechtigungsfehler beheben, durchläuft aber den kompletten Verzeichnisbaum und kann deshalb bei grossen Volumes langsam sein.
Der Inhalt eines Volumes ist nicht zwingend konsistent, zum Beispiel bei einer laufenden Datenbank. Empfehlungen dazu findest du unter Backup.
Backup
Alle Volume-Daten werden in das Managed Backup deines Servers aufgenommen. Trotzdem empfehlen wir zusätzliche Backups auf Anwendungsebene, aus zwei Gründen:
- Aus einem Volume-Backup lassen sich keine einzelnen Dateien oder Verzeichnisse wiederherstellen, sondern nur das gesamte Volume.
- Je nach Workload, zum Beispiel bei einer laufenden Datenbank, sind die Daten im Volume zum Zeitpunkt des Backups möglicherweise nicht konsistent.
Wir empfehlen deshalb, regelmässig Sicherungen wichtiger Anwendungsdaten zu erstellen, zum Beispiel Datenbank-Dumps, und diese in ein gemountetes Volume zu schreiben. Diese Dateien werden dann von unserer regulären Backup-Routine gesichert.