Podman containers
Manage OCI containers with Podman on this Linux host. Day-to-day automation is CLI; Cockpit + cockpit-podman is the optional web UI.
Overview
Podman runs the same class of images as Docker (OCI / Docker Hub / GHCR) but is a separate engine. It does not control the Docker daemon. Rootless is the default preference on this host.
This host context (keep current):
| Stack | Role |
|---|---|
| Podman | Preferred for new app containers (agent-managed) |
| cockpit-podman | Containers tab in Cockpit (Podman only) |
| Docker | May still be installed — do not assume shared running state |
| libvirt / cockpit-machines | Full VMs (e.g. windows-11) — not containers |
| Cockpit URL | https://localhost:9090 (cockpit.socket → port 9090) |
When to Use
- Create / start / stop / remove containers
execcommands or a shell inside a container- Build images, pull, tag, push (when auth allows)
- Compose stacks (
podman compose/ Compose YAML) - Logs, port checks, volume cleanup
- User mentions Cockpit containers, rootless Podman, or “spin up a service in a container”
Don't use for:
- Full OS desktops that need their own kernel, GPU passthrough, or Windows → libvirt VM
- Managing containers that were started with
docker(different runtime; list withdocker ps, notpodman ps) - Kubernetes cluster ops (Podman is single-host;
podman play kubeis not a cluster)
Prerequisites
# Install engine + Cockpit UI (Ubuntu/Debian)
sudo apt update
sudo apt install -y podman cockpit-podman
# Optional: user API socket (Cockpit / clients)
systemctl --user enable --now podman.socket
# socket: unix:///run/user/$(id -u)/podman/podman.sock
# Sanity
podman version
podman info --format '{{.Host.Security.Rootless}} {{.Host.Arch}}'
Completion: podman version works without sudo; Cockpit shows a Podman / containers section after reload or re-login.
Operating rules
- Prefer rootless (
podmanas the user). Only usesudo podman/ rootful when the workload requires it (privileged ports <1024 without redirect, some devices) — say so before doing it. - Name everything —
--nameon run; consistent compose project names. Never leave anonymous one-offs without a label if they outlive the command. - Publish ports explicitly —
-p HOST:CONTAINER. Prefer high host ports when rootless (e.g.8080:80). - Persist data in volumes or bind mounts — not container writable layer — for anything the user might care about after recreate.
- Tear down completely when asked to “remove/break down”: stop → rm container → remove unused vols/networks only if they were created for that workload (don't global-prune by default).
- VMs stay VMs — never “fix” a libvirt domain with a container destroy. Check
virsh/ Cockpit Machines only when the user asked about VMs. - Docker coexistence — if both exist, state commands must use the matching CLI. Confirm with
podman ps -aand, only if relevant,docker ps -a.
Core workflow
0) Discover
podman ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}\t{{.Ports}}'
podman images
podman volume ls
podman network ls
podman info
Done when you know what already exists and won't collide on name/port.
1) Run (one-shot or detached service)
# detached named service, port publish, restart policy
podman run -d \
--name SERVICE \
--restart unless-stopped \
-p HOSTPORT:CONTAINERPORT \
-v VOL_OR_PATH:/data:Z \
-e KEY=value \
IMAGE:TAG
# foreground / one-shot (auto-remove)
podman run --rm -it IMAGE:TAG CMD
Notes:
- SELinux hosts:
:Z/:zon binds when needed; on Ubuntu often unnecessary. - Rootless + host port <1024 often fails — use higher host port or rootful deliberately.
Done when podman ps shows the container healthy/up and podman port SERVICE matches intent.
2) Exec / send commands
podman exec SERVICE CMD [ARGS...]
podman exec -it SERVICE /bin/sh # or bash if present
podman logs -f --tail 200 SERVICE
podman top SERVICE
Done when exit code and stdout/stderr are captured for the user (or error explained).
3) Lifecycle
podman stop SERVICE
podman start SERVICE
podman restart SERVICE
podman rm SERVICE # must be stopped unless -f
podman rm -f SERVICE # force
podman image rm IMAGE
4) Compose
# from directory with compose.yaml / docker-compose.yml
podman compose up -d
podman compose ps
podman compose logs -f
podman compose exec SERVICE CMD
podman compose down # containers + default network
podman compose down -v # also volumes — only if user wants data gone
If podman compose is missing, try podman-compose or install compose support; don't silently fall back to Docker Compose against dockerd unless the user asked for Docker.
Done when services are up (or fully down) per podman compose ps / podman ps.
5) Build
podman build -t NAME:TAG -f Containerfile .
# Dockerfile works too
podman build -t NAME:TAG .
6) Inspect / debug
podman inspect SERVICE --format '{{.State.Status}} {{.RestartCount}}'
podman inspect SERVICE --format '{{json .NetworkSettings}}' | head -c 2000
podman events --filter container=SERVICE --since 10m
ss -lntp | grep HOSTPORT || true
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:HOSTPORT/ || true
Networking cheat sheet
| Goal | Flag / action |
|---|---|
| Publish port | -p 8080:80 |
| Host network (Linux) | --network host (less isolation) |
| User-defined network | podman network create NAME then --network NAME |
| DNS between containers | same custom network; use container names |
| Rootless port issues | higher host port; or net.ipv4.ip_unprivileged_port_start |
Volumes
podman volume create APP-data
podman run -v APP-data:/var/lib/app:Z ...
podman volume inspect APP-data
# bind mount
podman run -v /home/tim/app-data:/data:Z ...
Destroy path: podman rm then podman volume rm APP-data only if disposable.
systemd inside a container
Needed for multi-service “mini VMs” when services expect PID 1 systemd. Prefer a custom entrypoint for xrdp (see RDP section) — rootless --systemd=always is flaky (systemctl is-system-running hangs; dbus zombies).
podman run -d --name arch-sys \
--systemd=always \
--hostname arch-sys \
-p 13389:3389 \
archlinux:latest
Prefer a purpose-built image + entrypoint over long manual exec sessions when repeating.
Desktop RDP container (proven on this host)
Goal: remote XFCE desktop over RDP into a persistent Podman container. Client tested: wlfreerdp3.
Base image choice
| Base | xrdp | Notes |
|---|---|---|
| Ubuntu 24.04 | apt install xrdp xorgxrdp |
Preferred — packages in universe |
| Arch | AUR only (xrdp / xorgxrdp) |
XFCE via pacman is fine; AUR build is slow/fragile in containers |
Do not fight Arch AUR for first RDP — use Ubuntu.
Persistence pattern (required for durable desktops)
- Install packages + write
/usr/local/bin/rdp-entrypoint.sh(starts dbus, xrdp-sesman, xrdp, thensleep infinity) podman commit→ e.g.localhost/rdp-xfce:persistentwithENTRYPOINTset- Named volumes:
rdp-home→/home/USER,rdp-xrdp-etc→/etc/xrdp - Run with
--restart unless-stoppedand high host port (rootless):-p 13389:3389 - Host:
loginctl enable-linger $USERso rootless containers return after reboot - Optional note file:
~/Documents/rdp-container.md
podman volume create rdp-home
podman volume create rdp-xrdp-etc
podman run -d --name rdp --hostname rdp --restart unless-stopped \
-p 13389:3389 \
-v rdp-home:/home/tim \
-v rdp-xrdp-etc:/etc/xrdp \
localhost/rdp-xfce:persistent
Recreate keeps data if volumes are kept; only image/entrypoint need reinstall if you rm -f without volumes.
Entrypoint essentials
startwm.sh/.xsession→startxfce4/xfce4-sessionunset DBUS_SESSION_BUS_ADDRESSandXDG_RUNTIME_DIRbefore session (avoids black screen)- Start
/usr/sbin/xrdp-sesman --nodaemonthen/usr/sbin/xrdp --nodaemonin background; PID 1 =sleep infinity(or systemd if it works) - Create login user + password inside image and ensure volume-mounted
/homegets.xsessionon first boot
Connect
wlfreerdp3 /v:127.0.0.1:13389 /u:tim /p:PASSWORD /cert:ignore
# optional: /size:1920x1080 /network:auto
Port / concurrency pitfalls (hard lessons)
- One host port owner — only one container may publish
13389. Arch with--restart unless-stoppedwill steal the port after reboot/kill and blockrdpstart (pasta: Address already in use). - Fix:
podman update --restart=no OTHER;podman stop OTHER; free pasta withfuser -k 13389/tcpif needed; thenpodman start rdp. - Exit 137 during heavy
aptis often OOM-kill of the exec or cgroup pressure — retry install; host may still show free RAM. - Stale apt locks after interrupted installs: clear
/var/lib/dpkg/lock*inside container,dpkg --configure -a, re-run apt. - TCP probe to 3389 is not a full RDP handshake —
libxrdp_force_readerrors in logs frombash /dev/tcpare expected; usewlfreerdp3to validate. - Building large apps (e.g. Ladybird) → put source under the home volume; install Qt 6.9+ via
aqtinstallif distro Qt is < 6.9; useclang-21+ Kitware CMake ≥ 3.30 (see ladybird-build skill).
This host’s current RDP stack (keep current)
| Item | Value |
|---|---|
| Container | rdp |
| Image | localhost/rdp-xfce:persistent |
| Port | 13389 |
| Volumes | rdp-home, rdp-xrdp-etc |
| Restart | unless-stopped |
| User | tim (password set at create time) |
| Docs | ~/Documents/rdp-container.md |
Cockpit
- URL: https://localhost:9090
- Package:
cockpit-podman - UI manages Podman only (not Docker, not libvirt)
- Agent still prefers CLI for automation; mention UI when user is clicking around
Install reference also lives at ~/Documents/cockpit-install.md on this machine.
Decision: container vs VM
| Need | Choose |
|---|---|
| App/service, API, DB, CLI toolchain | Podman container |
| Full desktop + RDP (toy/sandbox) | Ubuntu container + xrdp (persistence pattern); VM if daily driver / GPU |
| Windows, other kernel, GPU passthrough, nested virt | libvirt VM (Cockpit Machines) |
| Kernel modules, custom kernel | VM |
Safety
- Do not expose RDP/SSH/DB ports to
0.0.0.0on untrusted networks without saying so; prefer localhost publish or firewall. - Do not
podman system prune -a --volumesunless the user explicitly wants a wide cleanup — it deletes unused images and volumes. - Do not remove Docker resources with Podman or vice versa.
- Secrets: prefer env files with restricted perms or podman secrets; avoid pasting long-lived tokens into image layers.
Common pitfalls
podmannot found — installpodman(andcockpit-podmanif UI wanted); recheck PATH.- Container “missing” in Cockpit — it was started with Docker, or rootful vs rootless mismatch (root sees different containers than user).
- Permission denied on port — rootless binding low port; remap host port ≥1024.
- Data vanished after recreate — forgot volume; always mount before declaring the service durable.
- Using
dockercommands out of habit — only when user wants Docker; default new work to Podman once installed. - Assuming Arch/Ubuntu container = that distro’s kernel — always host kernel.
- Force-rm production-looking names without confirm — if name suggests user data (
*prod*,*db*,windows*), confirm scope first.windows-11is a VM, not a container. - RDP port stolen by another container’s restart policy — disable restart on competitors; only one publisher per host port.
- Arch for xrdp — AUR-only; prefer Ubuntu image for desktop RDP unless user insists on Arch.
- Ephemeral desktop — packages lost on
rmwithout commit/volumes; always use persistence pattern above for RDP.
Verification checklist
After any create/change:
-
podman ps -ashows expected name and status - Ports:
podman port NAMEand/or local curl/connect test - Logs clean enough to explain failures (
podman logs --tail 100 NAME) - Persistent paths use volumes/binds when required
- Teardown (if requested): container gone; named vols/networks removed only as agreed
- Did not touch libvirt VMs or Docker resources unless asked
One-shot recipes
Smoke test
podman run --rm hello-world
podman run --rm -d --name nginx-smoke -p 8080:80 docker.io/library/nginx:alpine
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/
podman rm -f nginx-smoke
Ephemeral shell
podman run --rm -it docker.io/library/alpine:latest /bin/sh
Tear down one named service hard
podman stop SERVICE 2>/dev/null; podman rm -f SERVICE
# optional: podman volume rm SERVICE-data
Persistent RDP desktop (summary)
# after image localhost/rdp-xfce:persistent exists (see Desktop RDP section)
podman run -d --name rdp --restart unless-stopped -p 13389:3389 \
-v rdp-home:/home/tim -v rdp-xrdp-etc:/etc/xrdp \
localhost/rdp-xfce:persistent
wlfreerdp3 /v:127.0.0.1:13389 /u:tim /p:PASSWORD /cert:ignore
Agent completion style
When the user asks to manage containers: run real commands, report names/ports/status from tool output, and leave the system in the state they asked for (up or fully down). Prefer exact command echoes in summaries when they like brief/command-oriented replies.