Dieser Post ist Teil der Serie über mein privates BeagleBone-Black-Projekt. Die CI/CD-Infrastruktur des Projekts läuft auf Drone CI — aber nicht mit Docker, sondern mit Podman als Container-Backend. Was auf dem Papier einfach klingt, hat in der Praxis ein paar Ecken.
Warum Podman statt Docker?
Die kurze Antwort: rootless-Betrieb und kein Daemon.
Docker erfordert einen privilegierten Daemon der permanent läuft — dockerd —
und der Zugriff auf den Docker-Socket bedeutet de facto Root-Rechte auf dem Host.
Das ist für viele Umgebungen ein akzeptables Tradeoff, für meinen privaten Server nicht.
Podman läuft ohne Daemon:
# Docker: Client spricht mit Daemon der als root läuft
docker run ubuntu echo hello
# Podman: kein Daemon, direkter fork/exec als normaler User
podman run ubuntu echo helloKonkrete Vorteile im Betrieb:
Kein dauerhaft laufender privilegierter Prozess
Container laufen unter dem eigenen UID des aufrufenden Users
podman-auto-updatestatt Docker WatchersDrop-in-kompatibel zu Docker für die meisten Fälle
Podman ist kein perfekter Docker-Ersatz. Wo es Unterschiede gibt, beschreibe ich sie weiter unten bei den Fallstricken. |
Drone CI Serverinstallation mit Podman
Drone Server und Runner laufen selbst als Container — gestartet mit Podman.
Drone Server
podman run \
--detach \
--name=drone \
--restart=always \
--env=DRONE_GITEA_SERVER=https://gitea.example.com \
--env=DRONE_GITEA_CLIENT_ID=<client-id> \
--env=DRONE_GITEA_CLIENT_SECRET=<secret> \
--env=DRONE_RPC_SECRET=<shared-secret> \
--env=DRONE_SERVER_HOST=drone.example.com \
--env=DRONE_SERVER_PROTO=https \
--publish=80:80 \
--publish=443:443 \
--volume=/var/lib/drone:/data \
docker.io/drone/drone:2Drone Runner (Podman-Backend)
Der Standard-Drone-Docker-Runner spricht gegen den Docker-Socket. Für Podman gibt es zwei Wege:
Option A — Docker-kompatiblen Socket exponieren:
Podman kann einen Docker-kompatiblen Socket bereitstellen:
systemctl --user enable --now podman.socket
# Socket liegt unter: /run/user/<UID>/podman/podman.sockDen Runner dann mit dem Podman-Socket starten:
podman run \
--detach \
--name=drone-runner \
--restart=always \
--env=DRONE_RPC_PROTO=https \
--env=DRONE_RPC_HOST=drone.example.com \
--env=DRONE_RPC_SECRET=<shared-secret> \
--env=DRONE_RUNNER_CAPACITY=2 \
--volume=/run/user/1000/podman/podman.sock:/var/run/docker.sock \
docker.io/drone/drone-runner-docker:1Option B — Exec Runner (kein Container-Socket nötig):
Der Exec Runner führt Steps direkt auf dem Host aus — einfacher, weniger isoliert:
podman run \
--detach \
--name=drone-runner-exec \
--restart=always \
--env=DRONE_RPC_PROTO=https \
--env=DRONE_RPC_HOST=drone.example.com \
--env=DRONE_RPC_SECRET=<shared-secret> \
--volume=/var/run/drone-runner:/data \
docker.io/drone/drone-runner-exec:latestIch verwende Option A weil der Docker-Runner die bessere Step-Isolation bietet
und .drone.yml-Dateien ohne Anpassung funktionieren.
Fallstricke
Volume-Mounts und Dateiberechtigungen
Das größte Problem beim rootless Betrieb: Dateiberechtigungen.
Rootless Podman mappt UIDs im Container auf Subuid-Ranges des Host-Users.
Ein Prozess der im Container als root (UID 0) läuft, läuft auf dem Host als
UID des Users plus Offset.
Problem: Ein Build-Step schreibt Dateien unter root im Container —
nach dem Build gehören diese Dateien auf dem Host einem UID der nicht existiert.
Lösung: Explizites UID-Mapping oder --userns=keep-id:
# .drone.yml
steps:
- name: build
image: ubuntu:22.04
volumes:
- name: workspace
path: /workspace
environment:
DRONE_WORKSPACE_BASE: /workspace
volumes:
- name: workspace
host:
path: /tmp/drone-workspaceOder den Drone-Runner mit --userns=keep-id starten, damit der Container-User
mit dem Host-User übereinstimmt.
Netzwerk-Isolation zwischen Steps
Docker-Runner-Steps im selben Pipeline-Run teilen ein Netzwerk.
Bei Podman mit rootless-Setup kann das Netzwerk-Backend (slirp4netns vs pasta)
zu unterschiedlichen Ergebnissen führen.
Problem: Ein Datenbankcontainer in Step A ist aus Step B unter localhost
nicht erreichbar.
Lösung: Service-Container explizit benennen und per Hostname ansprechen:
services:
- name: postgres
image: postgres:15
environment:
POSTGRES_DB: testdb
POSTGRES_PASSWORD: test
steps:
- name: test
image: golang:1.21
commands:
# Hostname ist der Service-Name, nicht localhost
- go test -db-host=postgres ./...Secrets und Environment-Variablen
Drone-Secrets funktionieren unverändert — das ist kein Podman-spezifisches Problem. Aber: bei rootless Podman dürfen Secrets nicht in gemountete Host-Verzeichnisse geschrieben werden die anderen Users zugänglich sind.
Best Practice: Secrets nur als Umgebungsvariablen, nie als Dateien in /tmp.
steps:
- name: deploy
image: alpine
environment:
SSH_KEY:
from_secret: ssh_private_key
commands:
# Key in memory halten, nie auf Disk
- eval $(ssh-agent -s)
- echo "$SSH_KEY" | ssh-add -
- ssh user@host "systemctl restart myservice"Cross-Compilation und privilegierte Operationen
Mein Build-Setup cross-kompiliert für ARMv7.
Der Cross-Kompilierungs-Container braucht keine besonderen Rechte —
aber ich bin anfangs in die Falle getappt, --privileged zu setzen weil ein
anderer Schritt es brauchte.
Regel: Jeden Step auf minimale Rechte reduzieren.
--privileged nur wenn wirklich nötig (z. B. Kernel-Module laden).
Cross-Kompilierung braucht es nie.
Funktionierende .drone.yml-Konfiguration
Das ist meine tatsächliche Pipeline — vereinfacht aber funktionierend:
kind: pipeline
type: docker
name: beaglebone-build
steps:
- name: build-c
image: ubuntu:22.04
commands:
- apt-get update -q && apt-get install -y cmake gcc-arm-linux-gnueabihf
- cmake -DCMAKE_TOOLCHAIN_FILE=cmake/armv7-toolchain.cmake -B build-arm
- cmake --build build-arm
- name: build-rust
image: rust:1.75
commands:
- rustup target add armv7-unknown-linux-gnueabihf
- cd rust
- cargo build --release --target armv7-unknown-linux-gnueabihf
- name: build-go
image: golang:1.21
commands:
- cd go-api
- go build ./...
- go test ./...
- name: deploy
image: alpine
environment:
SSH_KEY:
from_secret: bbb_ssh_key
commands:
- apk add --no-cache openssh-client rsync
- eval $(ssh-agent -s)
- echo "$SSH_KEY" | ssh-add -
- rsync -avz build-arm/ pi@beaglebone:/opt/bbb-hal/
- ssh pi@beaglebone "systemctl restart bbb-hal"
when:
branch:
- mainFazit
Podman als Drone-CI-Backend funktioniert gut — mit ein paar Anpassungen.
Wann Podman sich lohnt:
Kein Docker-Daemon soll dauerhaft laufen
Rootless-Betrieb ist eine Anforderung
Privater Server ohne komplexe Container-Orchestrierung
Wann Docker einfacher ist:
Große Teams mit bestehenden Docker-Workflows
Viele Images die Docker-Socket direkt nutzen
Wenn Kompatibilitätsprobleme mehr Zeit kosten als der Daemon-Betrieb
Für mein privates Projekt ist Podman die richtige Wahl. Die Fallstricke waren lösbar, und das Ergebnis ist eine CI-Infrastruktur die ohne privilegierte Hintergrunddienste auskommt.
Nächster Post in der Serie: Cross-Kompilierung für ARMv7 mit CMake