Podman primer

Posted on Mar 26, 2023

Yet another post for my future self, this time documenting how I currently setup podman containers on my overly complicated home network.

It’s worth noting that I’m using the version of podman (3.4.4) shipped with Ubuntu 22.04, and that version 4 is supposed to be a lot better in some way. I haven’t had any real issues with my setup outlined here so I have been too lazy to try to upgrade. :)

Creating networks

I have multiple networks in my setup – one for iot, one for management, one for my webserver…all due to reasons. I’m using vlans to separate these networks, and send tagged traffic on my main interface.

In most cases I want my containers to live on their designated vlan and not on a default host switch. For this I use macvlan interfaces, and base these subinterfaces on the bridge name.

podman network create \
  -d macvlan \
  -o parent=br-network \
  --subnet 192.0.2.0/24 \
  --gateway 192.0.2.1 \
  --ip-range 192.0.2.0/24 \
  examplenet

I’m not sure it’s correct to hang these on each bridge, on FreeBSD I’d just create a bridge, add the vlan subinterface and then add each epair to the bridge.

Creating secrets

I’ve been bitten multiple times by line endings in secrets, as I’ve configured my editors to include one at the end by default. The best way to create secrets is to use printf like below.

printf "supersecret" | podman secret create example-secret -

You can replace the dash by a file path instead, but be careful with these line endings.

Create container

I use create and not run, letting systemd manage starting the container. As I typically use macvlan I don’t specify ports as it’s meaningless in that case.

podman create \
  --name example \
  --ip=192.0.2.3 \
  --network examplenet \
  -e TZ=Europe/Stockholm \
  --secret example-secret,type=env,target=EXAMPLE_SECRET \
  -v "/tmp:/tmp" \
  --memory 128M \
  --cpus=1 \
  hello-world

Create systemd service

Using --new when generating the systemd service file makes systemd recreate it on each start. It’s necessary to use if you want podman auto-update to work.

podman generate systemd --new example > /etc/systemd/system/container-example.service
systemctl daemon-reload
systemctl enable --now container-example.service

Auto update containers

You need to add a label (at container creation) for each container you want to auto update. If you forgot to, but used --new with podman generate systemd, then you can just add the label in the service file.

podman create --label io.containers.autoupdate=registry ...

To manually check if your containers have updates (remove --dry-run for doing the update):

podman auto-update --dry-run

I’m not sure if podman-auto-update.timer is enabled by default, but it might be. Otherwise that timer will run once every 24h and update, restart and rollback (if necessary) your containers.

Some examples

Here are a few containers I’m currently using with podman at home:

  • postgres14
  • grafana-oss
  • navidrome
  • radicale
  • loki
  • promtail
  • miniflux
  • restic-restserver
  • caddy
  • drone
  • drone-runner
  • omada-controller

You might notice that I don’t export any ports and assign all IPs manually. Each container effectively has their own networking namespace.

drone-runner

This one makes me nervous, as I’m effectively giving this container root on my host system. :-)

podman create \
--label io.containers.autoupdate=registry \
--name runner \
--ip=192.0.2.5 \
--dns=192.0.2.2 \
--network foobarnet \
--secret drone_gitea_rpc_secret,type=env,target=DRONE_RPC_SECRET \
--secret drone_server_host,type=env,target=DRONE_RPC_HOST \
--secret drone_server_proto,type=env,target=DRONE_RPC_PROTO \
-e DRONE_RUNNER_CAPACITY=1 \
-e DRONE_RUNNER_NAME="runner" \
--memory 2048M \
--cpus=2 \
-v /run/podman/podman.sock:/var/run/docker.sock \
docker.io/drone/drone-runner-docker

It seems drone-runner is fine to use the podman socket instead of the default docker socket.

restic-restserver

Nothing special here, just another example.

podman create \
--label io.containers.autoupdate=registry \
--name rest_server \
--ip=192.0.2.10 \
--dns=192.0.2.2 \
--network baznet \
-v "/path/to/restic/:/data" \
-e OPTIONS="--private-repos --listen :8000 --append-only --prometheus" \
--cpus=1 \
--memory 1G \
restic/rest-server

radicale

I just copied this from dockerhub. :-)

podman create \
--label io.containers.autoupdate=registry \
--name radicale \
--ip=192.0.2.15 \
--dns=192.0.2.2 \
--network abcnet \
--init \
--read-only \
--cap-drop ALL \
--cap-add CHOWN \
--cap-add SETUID \
--cap-add SETGID \
--cap-add KILL \
--pids-limit 50 \
--memory 256M \
--cpus=1 \
--health-cmd="curl --fail http://localhost:5232 || exit 1" \
--health-interval=30s \
--health-retries=3 \
-v /path/to/radicale:/data \
tomsquest/docker-radicale