Container am Beispiel Podman

Warum Container? (Motivation und Use-Cases)

Container vereinfachen den Test und Betrieb von Server-Diensten. Ein Container verpackt eine Software zusammen mit all ihren Abhängigkeiten in eine (transportable) Einheit.

Exkurs: Was sind Abhängigkeiten (Dependencies)?

Software läuft selten isoliert. Sie benötigt eine spezifische Umgebung:

  • Laufzeitumgebungen (Runtimes): z. B. Python 3.11, Java 17 oder Node.js.

  • Bibliotheken: Sammlungen von Hilfsfunktionen (z. B. für Verschlüsselung oder Grafik).

  • System-Tools: Programme für die Netzwerkkommunikation oder Dateiverarbeitung.

  • Konfigurationen: Pfade, Umgebungsvariablen oder Zeitzoneneinstellungen.

Fehlt nur eine dieser Komponenten oder liegt sie in einer falschen Version vor, startet die Software nicht ("Dependency Hell").

In der Softwareentwicklung

In der klassischen Entwicklung tritt oft die "Umgebungsdrift" auf: Software funktioniert auf dem Laptop, scheitert aber im Rechenzentrum aufgrund minimaler Unterschiede im Betriebssystem. Container lösen dies durch Kapselung.

  • Lösung der „Dependency Hell“: Da das Container-Image unveränderlich (immutable) ist, verhält sich die Anwendung in der Testumgebung exakt so wie in der Produktion.

  • Architekturmodell Microservices: Anwendungen werden nicht mehr als riesige "Monolithen" gebaut, sondern in kleine, lose gekoppelte Einheiten zerlegt. Container ermöglichen es, diese Bausteine unabhängig voneinander zu entwickeln und zu aktualisieren.

  • Effizienz in CI/CD-Pipelines: Testumgebungen können in Sekunden automatisiert auf- und abgebaut werden. Das beschleunigt den Zyklus von der Code-Änderung bis zur Veröffentlichung (Feedback-Loop).

Für den Betrieb von Anwendungen (Operations)

Für Administrator*innen bieten Container entscheidende Vorteile bei der Verwaltung von Servern:

  • Portabilität: Ein OCI-konformer Container läuft überall dort, wo eine Container-Engine (wie Podman) installiert ist – egal ob auf einem lokalen Server, im Mainframe oder in der Cloud.

  • Skalierbarkeit: Bei hoher Last (z. B. ein Ansturm auf einen Webshop) können binnen Millisekunden zusätzliche Instanzen derselben Anwendung gestartet werden.

  • Saubere Host-Systeme: Da alle Bibliotheken im Container liegen, muss auf dem Host-Server kaum noch Software installiert werden. Das Betriebssystem bleibt "schlank" und stabil.

  • Einfaches Rollback: Wenn ein Update fehlschlägt, stoppt man einfach den neuen Container und startet den alten wieder. Der Zustand davor ist sofort wiederhergestellt.

Sicherheit und Isolation

Ein oft unterschätzter Use-Case ist die Sicherheit durch Prozess-Isolation:

  • Wenn eine Anwendung in einem Container kompromittiert wird, bleibt der Angreifer (bei korrekter Konfiguration) im Container gefangen. Er sieht keine Dateien des Hosts und keine anderen Prozesse.

  • Podman-Besonderheit: Da Podman "Rootless" (ohne Administrator*innenrechte) laufen kann, hat ein Angreifer selbst bei einem Ausbruch aus dem Container keine Root-Rechte auf dem echten Server.

Kern-Unterschied zur Virtuellen Maschinen (VMs)

  • Virtuelle Maschinen (VMs) abstrahieren die Hardware mithilfe eines Hypervisors, um vollständige, isolierte Gast-Betriebssysteme zu betreiben. Dank moderner CPU-Erweiterungen (wie Intel VT-x/AMD-V) wird die Hardware dabei nicht komplett softwareseitig emuliert, sondern hocheffizient direkt an den echten Prozessor durchgereicht.
  • Container virtualisieren direkt das Betriebssystem und isolieren gezielt einzelne Anwendungen auf Prozessebene. Da sich alle Container den Kernel des Host-Systems teilen, entfällt der Ressourcen-Overhead eines eigenen Betriebssystems pro Instanz.
  • Das Ergebnis: Container starten in Sekundenbruchteilen und sind im Vergleich zu VMs extrem leichtgewichtig.

Technologischen Säulen

Ein Container ist kein monolithisches Produkt, sondern ein geschicktes Zusammenspiel nativer Linux-Kernel-Features. Man spricht auch von OS-Level Virtualization.

  • chroot (1979): Der „Urahn“ der Isolation. Er ändert das Root-Verzeichnis für einen Prozess. Dieser sieht nur noch die Dateien innerhalb eines festgelegten Unterordners und hält diesen für das gesamte System (/).
  • Namespaces (ab 2002): Sie bestimmen, was ein Prozess sehen darf. Namespaces isolieren Ressourcen wie Prozess-IDs (PID), Netzwerkschnittstellen (NET) und Mount-Punkte (MNT). So kann ein Container eine eigene IP-Adresse und eine eigene Prozess-Liste besitzen, ohne den Host oder andere Container zu beeinflussen.
  • Control Groups (cgroups): Sie regeln, was ein Prozess nutzen darf. Mit cgroups werden Ressourcen wie CPU, Arbeitsspeicher (RAM) und Festplatten-I/O begrenzt. Dies verhindert, dass ein einzelner Container (z. B. durch einen Programmierfehler) den gesamten Host-Server lahmlegt („Noisy Neighbor“-Problem).
  • Union File Systems (z. B. OverlayFS): Diese ermöglichen das Übereinanderschichten von Dateisystemen (Layer). Ein Image besteht aus mehreren unveränderlichen Schichten; nur die oberste Schicht ist beschreibbar. Das macht Container extrem platzsparend und schnell beim Start.

Docker war 2013 der erste populäre „Überbau“, der diese komplexen Kernel-Technologien unter einer einfachen Oberfläche zusammenführte und durch ein standardisiertes Image-Format massentauglich machte. Heute folgen moderne Tools wie Podman denselben Prinzipien, setzen dabei aber auf eine noch stärkere Integration in moderne Linux-Sicherheitsfeatures.

Der OCI-Standard (Open Container Initiative)

Die Open Container Initiative (OCI) wurde 2015 gegründet, um offene, herstellerunabhängige Industriestandards für Container-Formate und Laufzeiten zu schaffen. Ziel war es, die Abhängigkeit von einer einzelnen Firma (Vendor Lock-in) zu verhindern und Interoperabilität sicherzustellen.

Man kann sich den OCI-Standard wie die Normierung von Schiffscontainern vorstellen: Es ist egal, wer den Container gebaut hat oder welcher Kran ihn hebt – sie passen immer zusammen.

Die drei zentralen Spezifikationen:

  • Image Specification (image-spec): Definiert das Format der Container-Images. Ein OCI-Image ist im Kern ein strukturiertes Tar-Archiv, das die Dateisystem-Layer und Metadaten (wie Startbefehle oder Umgebungsvariablen) enthält.

  • Runtime Specification (runtime-spec): Legt fest, wie ein Container ausgeführt wird. Sie beschreibt den Lebenszyklus (Starten, Stoppen, Löschen) und die Konfiguration, mit der die Host-Ressourcen isoliert werden.

  • Distribution Specification (dist-spec): Standardisiert den Austausch von Images zwischen Clients (wie Podman) und Servern (Registries wie Docker Hub oder Quay.io).

Bedeutung für die Praxis

Dank OCI ist die Container-Welt austauschbar geworden:

  • Ein Image, das mit Docker erstellt wurde, kann problemlos mit Podman gestartet werden.

  • Werkzeuge können spezialisiert werden: Während Docker ein "All-in-one"-Tool ist, nutzt Podman im Hintergrund oft die OCI-Referenz-Runtime runc, um die eigentliche Arbeit im Kernel zu erledigen.

Fazit: OCI sorgt dafür, dass Container-Technologie eine universelle Infrastruktur-Eigenschaft geworden ist (ähnlich wie TCP/IP für das Internet) und kein proprietäres Produkt bleibt.

Images und Container: Die Konzepte

In der Welt der Container unterscheiden wir strikt zwischen dem „Bauplan“ und der „Ausführung“.

Was ist ein Image?

Allgemein versteht man unter einem Image (Abbild) in der Informatik eine statische Datei, die den vollständigen Zustand eines Systems oder Datenträgers zu einem bestimmten Zeitpunkt speichert (ähnlich wie eine ISO-Datei einer DVD oder ein Backup-Image einer Festplatte).

Ein Container-Image ist ein spezielles, schreibgeschütztes (read-only) Paket, das alles enthält, was zum Ausführen einer Anwendung benötigt wird:

  • Der ausführbare Programmcode.

  • Die benötigten Laufzeitumgebungen und Bibliotheken.

  • Standard-Konfigurationen und Umgebungsvariablen.

Das Besondere: Ein Container-Image besteht aus verschiedenen Layern (Schichten). Wenn zwei verschiedene Images die gleiche Basis nutzen (z. B. das Betriebssystem Alpine Linux), müssen diese gemeinsamen Schichten nur einmal auf der Festplatte gespeichert werden.

Was ist ein Container?

Ein Container ist die lebendige Instanz eines Images. Wenn man ein Image startet, wird es in den Arbeitsspeicher geladen und als isolierter Prozess ausgeführt.

Man kann sich das Verhältnis wie in der objektorientierten Programmierung vorstellen:

  • Das Image ist die Klasse (die Definition/der Bauplan).
  • Der Container ist das Objekt (die konkrete Ausprägung/Instanz).

Die Merkmale eines Containers:

  • Flüchtig (Ephemeral): Ein Container ist darauf ausgelegt, jederzeit gestoppt, gelöscht und neu erstellt zu werden.
  • Schreibbar: Während das Image schreibgeschützt bleibt, erhält jeder Container beim Start eine hauchdünne, beschreibbare Schicht (Writable Layer) „oben drauf“. Dort landen Dateien, die die Anwendung während der Laufzeit erstellt.
  • Isoliert: Trotz gemeinsamer Hardware sieht ein Container nur seine eigenen Prozesse und Dateien.

Lebenszyklus

  • Build: Man schreibt eine Anleitung (z. B. ein Containerfile oder Dockerfile) und baut daraus ein Image.
  • Ship: Das Image wird in einer Registry (Speicherort) abgelegt.
  • Run: Mit Podman lädt man das Image herunter und startet es als Container.

Die drei Ebenen der Bereitstellung

  • Registry: Die Bibliothek (z. B. quay.io oder docker.io). Dort liegen tausende fertige Images für Webserver, Datenbanken oder Python-Umgebungen.
  • Local Storage: Dein lokaler PC. Hier landen die Images nach einem pull-Befehl. Sie verbrauchen Platz, tun aber noch nichts.
  • Runtime (Podman): Die Ausführungsschicht. Erst hier werden aus den statischen Images lebendige Container. Ein run-Befehl weist dem Image Ressourcen zu und startet es als isolierten Prozess im Kernel.

Von den Grundlagen zur Praxis: Arbeiten mit Podman

Um den Unterschied zwischen Images und Containern zu verstehen, hilft ein Blick auf die grundlegenden Befehle. Podman folgt hierbei der intuitiven Syntax, die durch Docker etabliert wurde.

Befehle für Images

Diese Befehle verwalten die statischen Baupläne auf deiner Festplatte.

  • podman pull <image>: Lädt ein fertiges Abbild aus einer Registry (z. B. Docker Hub) herunter.
  • podman images: Listet alle lokal gespeicherten Images auf. Man sieht hier die Größe und die Image-ID.
  • podman rmi <image>: Löscht ein Image von der Festplatte (remove image).

Befehle für Container

Diese Befehle steuern die aktiven (oder gestoppten) Instanzen.

  • podman run <image>: Der wichtigste Befehl. Er erzeugt aus einem Image einen neuen Container und startet ihn sofort.
  • podman ps: Zeigt alle aktuell laufenden Container an.
  • podman ps -a: Zeigt alle Container an (auch die bereits beendeten oder abgestürzten).
  • podman stop <container_id>: Hält einen laufenden Prozess im Container an.
  • podman rm <container_id>: Löscht die Instanz des Containers (die beschreibbare Schicht wird vernichtet).

Zusammenfassung des Workflow

  1. pull: Registry $\rightarrow$ Local Storage
  2. run: Local Storage $\rightarrow$ Runtime/Container

Persistenz

Ein wichtiges Konzept von Containern ist ihre Flüchtigkeit (Ephemerality). Ein Container ist kein dauerhafter Server, sondern ein Prozess, der jederzeit ersetzt werden kann.

Das Problem: Die beschreibbare Schicht

Wenn ein Container gestartet wird, liegt über dem schreibgeschützten Image eine dünne, beschreibbare Schicht (Container Layer).

Alle Änderungen (neue Dateien, Datenbankeinträge) landen in dieser Schicht.

Aber: Wird der Container mit podman rm gelöscht, wird auch diese Schicht gelöscht. Die Daten sind unwiderruflich verloren.

Die Lösung: Volumes

Um Daten dauerhaft (persistent) zu speichern, nutzt man Volumes. Dabei wird ein Verzeichnis des Host-Rechners in den Container "eingeblendet" (gemountet).

  • Die Daten liegen physikalisch auf dem Host.
  • Löscht man den Container, bleiben die Daten auf dem Host erhalten.
  • Ein neuer Container kann dasselbe Volume einbinden und direkt auf den alten Datenbestand zugreifen.

Mehr dazu: Wie Volumes konkret konfiguriert werden – benannte Volumes gegenüber Bind-Mounts, Persistenz über Neustarts hinweg und die Optionen wie :ro oder :Z – zeigt ausführlich der Abschnitt Volumes in Compose.

Hands-on

podman version

podman info

podman run hello-world

Wir nutzen Alpine Linux, eine extrem kleine und schnelle Linux-Distribution.

podman run -it alpine /bin/sh
  • podman run: Erstellt und startet einen Container.
  • -i (interactive): Hält die Standardeingabe (STDIN) offen, auch wenn kein Terminal zugewiesen ist.
  • -t (terminal/tty): Weist dem Container ein Pseudo-Terminal zu. Nur mit -it kannst du interaktiv mit dem Container kommunizieren.
  • alpine: Das Image, das als Basis dient. Wenn es lokal nicht vorhanden ist, lädt Podman es automatisch herunter.
  • /bin/sh: Der Befehl, der im Container ausgeführt werden soll – in diesem Fall die Shell.

Hinweis: Bei der Minimal-Installation ist keine bash-Shell verfügbar.

Dann z.B.:

whoami
ls /
exit

Zweites Beispiel

Erzeugen eines Volume mit podman volume create website

  • podman volume inspect website

Starten eines Container mit einem nginx-Webserver

podman run --name mein-webserver -d \
   -v website:/usr/share/nginx/html:Z \
   -p 8180:80 \
   docker.io/library/nginx
  • -p 8180 leitet den Port auf der lokalen Maschine auf den Port 80 im Container weiter.

  • -v übergibt das Volume

  • -d startet den Container im Hintergrund

  • Mit podman exec -it mein-webserver bash kann man sich auf dem Container einloggen.

  • echo "<h1>Willkommen an der HTW!</h1>" > /usr/share/nginx/html/index.html

  • Im Browser des Hosts http://localhost:8180

exec vs. run: Beide Befehle können eine interaktive Shell öffnen, tun aber Grundverschiedenes.

  • podman run -it <image> /bin/sh (siehe Alpine-Beispiel oben) erzeugt einen neuen Container aus einem Image und startet darin die Shell als Hauptprozess. Beendet man die Shell mit exit, stoppt der Container.
  • podman exec -it <container> bash startet einen zusätzlichen Prozess in einem bereits laufenden Container (hier der im Hintergrund laufende mein-webserver). Mit exit verlässt man nur die Shell – der Container läuft weiter.

Kurz: run startet einen Container, exec steigt in einen laufenden ein. -it (interactive + tty) ist in beiden Fällen nötig, um interaktiv arbeiten zu können.

Container löschen:

podman stop mein-webserver
podman rm mein-webserver

Fehlersuche z.B. mit:

podman logs mein-webserver
podman ps

Hinweis: Dasselbe Volume lässt sich auch in einer compose.yaml deklarativ einbinden – inklusive der hier verwendeten SELinux-Option :Z. Siehe Volumes in Compose.

Auswahl an Befehlen

  • Ansehen aller (auch nicht laufender mittels -a) Container

    • podman ps -a
  • Starten eines Containers mit eventuellem Herunterladen aus dem Docker-Hub:

    • podman run <image-name>
  • Beenden von Containern

    • podman kill <container-name>
  • Docker Version anzeigen:

    • podman --version
  • Heruntergeladene Images anzeigen:

    • podman image ls
  • Löschen der Images, die nicht verwendet werden:

    • podman image prune
  • Erzeugen eines Volume:

    • podman volume create <volume-name>
  • Alle Container anhalten. Beachten Sie die Verwendung von Command Substitution:

  • podman stop $(podman ps -a -q)

Multi-Container-Anwendungen mit Compose

In der Praxis besteht eine Anwendung selten aus nur einem einzigen Container. Compose ist das Werkzeug, um mehrere Container gemeinsam zu definieren, zu starten und zu stoppen – deklarativ in einer einzigen compose.yaml.

Eine ausführliche Behandlung – Dateiformat, Befehle, Netzwerke, Volumes, .env-Dateien und YAML-Besonderheiten – findet sich im nächsten Kapitel:

Docker Compose / Podman Compose

Erstellung eigener Images: Der Build-Prozess

In der professionellen Softwareentwicklung werden Images oft auch selbst erstellt, um eigene Anwendungen mitsamt ihrer Laufzeitumgebung zu paketieren. Podman ermöglicht diesen Prozess durch eine automatisierte Bauanleitung.

Das Containerfile (Dockerfile)

Die Basis für jedes Image ist das Containerfile. Dabei handelt es sich um eine einfache Textdatei, die schrittweise definiert, wie das Image aufgebaut sein soll. Sie legt fest, welches Basis-Betriebssystem verwendet wird, welche Softwarepakete installiert werden müssen und welche Programmdateien in das Image kopiert werden sollen.

Der Build-Vorgang und das Schichtenmodell

Mit dem Befehl podman build wird dieses File verarbeitet. Jede Zeile in der Anleitung erzeugt eine neue, unveränderliche Schicht (Layer) im resultierenden Image.

Vorteile dieses Verfahrens:

  • Reproduzierbarkeit: Der gesamte Aufbau der Umgebung ist als Code dokumentiert (Infrastructure as Code). Das Image kann auf jedem System exakt gleich nachgebaut werden.
  • Effizienz durch Caching: Bei Änderungen am Quellcode müssen nicht alle Schritte neu ausgeführt werden. Podman erkennt unveränderte Instruktionen und nutzt die bereits vorhandenen Schichten (Cache) wieder, was den Prozess massiv beschleunigt.
  • Versionierung: Containerfiles können zusammen mit dem Programmcode in Versionsverwaltungssystemen (wie Git) gespeichert werden, wodurch jede Version der Software untrennbar mit ihrer benötigten Umgebung verknüpft ist.

Ausblick: Container-Orchestrierung mit Kubernetes

Podman ist ein hervorragendes Werkzeug, um Container auf einem einzelnen Rechner (z. B. deinem Laptop) zu verwalten. In der realen Welt laufen Anwendungen jedoch oft auf riesigen Server-Clustern. Hier stößt die manuelle Verwaltung an Grenzen.

Kubernetes (oft abgekürzt als K8s) ist das Betriebssystem für solche Cluster. Es fungiert als „Dirigent“ (Orchestrator) für Container.

Die Kernaufgaben von Kubernetes:

  • Scheduling: K8s entscheidet automatisch, auf welchem physischen Server noch genug Platz (CPU/RAM) ist, um einen neuen Container zu starten.

  • Self-Healing: Wenn ein Container abstürzt oder ein ganzer Server ausfällt, merkt Kubernetes das sofort und startet die betroffenen Container auf einem anderen Server neu.

  • Skalierung: Bei hohem Besucheraufkommen kann Kubernetes angewiesen werden, die Anzahl der laufenden Container-Instanzen automatisch zu erhöhen (Horizontal Scaling).

  • Service Discovery & Load Balancing: Kubernetes verteilt den eingehenden Datenverkehr automatisch auf die verfügbaren Instanzen einer Anwendung.

Der Bezug zu Podman

Podman wurde so entwickelt, dass es sehr nah an den Konzepten von Kubernetes arbeitet. Ein wichtiges Merkmal von Podman ist die Fähigkeit, sogenannte Pods zu erstellen. Ein Pod ist eine Gruppe von einem oder mehreren Containern, die sich Ressourcen teilen – genau die kleinste Einheit, die auch in Kubernetes verwaltet wird.

Merksatz:
Wenn Podman das Werkzeug ist, um einen einzelnen Motor (Container) zu warten, dann ist Kubernetes die Leitzentrale, die eine ganze Flotte von LKWs koordiniert.