So Podman is an open source container engine like Dockerāwith "full"1 Docker compatibility. IMO Podmanās main benefit over Docker is security. But how is it more secure? Keep readingā¦
Docker traditionally runs a daemon as the root user, and you need to mount that daemonās socket into various containers for them to work as intended (See: Traefik, Portainer, etc.) But if someone compromises such a container and therefore gains access to the Docker socket, itās game over for your host. That Docker socket is the keys to the root kingdom, so to speak.
Podman doesnāt have a daemon by default, although you can run a very minimal one for Docker compatibility. And perhaps more importantly, Podman can run entirely as a non-root user.2 Non-root means if someone compromises a container and somehow manages to break out of it, they donāt get the keys to the kingdom. They only get access to your non-privileged Unix user. So like the keys to a little room that only contains the thing they already compromised.2.5 Pretty neat.
Okay, now for the annoying parts of Podman. In order to achieve this rootless, daemonless nirvana, you have to give up the convenience of Unix users in your containers being the same as the users on the host. (Or at least the same UIDs.) Thatās because Podman typically3 runs as a non-root user, and most containers expect to either run as root or some other specific user.
The "solution"4 is user re-mapping. Meaning that you can configure your non-root user that Podman is running as to map into the container as the root user! Or as UID 1234. Or really any mapping you can imagine. If that makes your head spin, wait until you actually try to configure it. Itās actually not so bad on containers that expect to run as root. You just map your non-root user to the container UID 0 (root)ā¦ and Bobās your uncle. But it can get more complicated and annoying when you have to do more involved UID and GID mappingsāand then play the resultant permissions whack-a-mole on the host because your volumes are no longer accessed from a container running as host-rootā¦
Still, itās a pretty cool feeling the first time you run a ārootā container in your completely unprivileged Unix user and everything just works. (After spending hours of swearing and Duck-Ducking to get it to that point.) At least, it was pretty cool for me. If itās not when you do it, then Podman may not be for you.
The other big annoying thing about Podman is that because thereās no Big Bad Daemon managing everything, there are certain things you give up. Like containers actually starting on boot. Youād think thatād be a fundamental feature of a container engine in 2023, but youād be wrong. Podman doesnāt do that. Podman adheres to the āUnix philosophy.ā Meaning, briefly, if Podman doesnāt feel like doing something, then it doesnāt. And therefore expects you to use systemd for starting your containers on boot. Which is all good and well in theory, until you realize that means Podman wants you to manage your containers entirely with systemd. Soā¦ running each container with a systemd service, using those services to stop/start/manage your containers, etc.
Which, if you ask me, is totally bananasland. I donāt know about you, but I donāt want to individually manage my containers with systemd. I want to use my good old trusty Docker Compose. The good news is you can use good old trusty Docker Compose with Podman! Just run a compatibility daemon (tiny and minimal and rootlessā¦ donāt you worry) to present a Docker-like socket to Compose and boom everything works. Except your containers still donāt actually start on boot. You still need systemd for that. But if you make systemd run Docker Compose, problem solved!
This isnāt the āPodman Wayā though, and any real Podman user will be happy to tell you that. The Podman Way is either the aforementioned systemd-running-the-show approach or something called Quadlet or even a Kubernetes compatibility feature. Briefly, about those: Quadlet is ājustā a tighter integration between systemd and Podman so that you can declaratively define Podman containers and volumes directly in a sort of systemd service file. (Well, multiple.) Itās like Podman and Docker Compose and systemd and Windows 3.1 INI files all had a bastard love childāand itās about as pretty as it sounds. IMO, youād do well to stick with Docker Compose.
The Kubernetes compatibility feature lets you write Kubernetes-style configuration files and run them with Podman to start/manage your containers. It doesnāt actually use a Kubernetes cluster; it lets you pretend youāre running a big boy cluster because your command has the word ākubeā in it, but in actuality youāre just running your lowly Podman containers instead. It also has the feel of being a dev toy intended for local development rather than actual production use.5 For instance, thereās no way to apply a change in-place without totally stopping and starting a container with two separate commands. What is this, 2003?
Lastly, thereās Podman Compose. Itās a third-party project (not produced by the Podman devs) thatās intended to support Docker Compose configuration files while working more ānativelyā with Podman. My brief experience using it (with all due respect to the devs) is that itās total amateur hour and/or just not ready for prime time. Again, stick with Docker Compose, which works great with Podman.
Anyway, thatās all Iāve got! Use Podman if you want. Donāt use it if you donāt want. Iām not the boss of you. But you said you wanted content on Lemmy, and now youāve got content on Lemmy. This is all your fault!
1 Where āfullā is defined as: Not actually full.
2 Newer versions of Docker also have some rootless capabilities. But theyāve still got that stinky olā daemon.
2.5 Itās maybe not quite this simple in practice, because youāll probably want to run multiple containers under the same Unix account unless youāre really OCD about security and/or have a hatred of the convenience of container networking.
3 You can run Podman as root and have many of the same properties as root Docker, but then whatās the point? One less daemon, I guess?
4 Where āsolutionā is defined as: Something that solves the problem while creating five new ones.
5 Spoiler: Red Hatās whole positioning with Podman is like they see it is as a way for buttoned-up corporate devs to run containers locally for development while their āproductionā is running K8s or whatever. Personally, I donāt care how they position it as long as Podman works well to run my self-hosting shitā¦
Yeah sure.
Iām going to assume youāre starting from the point of having a second linux user also set up to use rootless podman. Thatās just following the same steps for setting up rootless podman as any other user, so there shouldnāt be too many problems there.
If you have wireguard set up and running already - i.e. with Mullvad VPN or your own VPN to a VPS - you should be able to run
ip link
to see a wireguard network interface. Mine is calledwg
. I donāt use wg-quick, which means I donāt have all my traffic routing through it by default. Instead, I use a systemd unit to bring up the WG interface and set up routing.Iāll also assume the UID you want to forward is
1001
, because thatās what Iām using. Iāll also useenp3s0
as the default network link, because thatās what mine is, but if yours iseth0
, you should use that. Finally, Iāll assume that192.168.0.0
is your standard network subnet - itās useful to avoid routing local traffic through wireguard.#YOUR_STATIC_EXTERNAL_IP#
should be whatever you get by callingcurl ifconfig.me
if you have a static IP - again, useful to avoid routing local traffic through wireguard. If you donāt have a static IP you can drop this line.[Unit] Description=Create wireguard interface After=network-online.target [Service] RemainAfterExit=yes ExecStart=/usr/bin/bash -c " \ /usr/sbin/ip link add dev wg type wireguard || true; \ /usr/bin/wg setconf wg /etc/wireguard/wg.conf || true; \ /usr/bin/resolvectl dns wg #PREFERRED_DNS#; \ /usr/sbin/ip -4 address add #WG_IPV4_ADDRESS#/32 dev wg || true; \ /usr/sbin/ip -6 address add #WG_IPV6_ADDRESS#/128 dev wg || true; \ /usr/sbin/ip link set mtu 1420 up dev wg || true; \ /usr/sbin/ip rule add uidrange 1001-1001 table 200 || true; \ /usr/sbin/ip route add #VPN_ENDPOINT# via #ROUTER_IP# dev enp3s0 table 200 || true; \ /usr/sbin/ip route add 192.168.0.0/24 via 192.168.0.1 dev enp3s0 table 200 || true; \ /usr/sbin/ip route add #YOUR_STATIC_EXTERNAL_IP#/32 via #ROUTER_IP# dev enp3s0 table 200 || true; \ /usr/sbin/ip route add default via #WG_IPV4_ADDRESS# dev wg table 200 || true; \ " ExecStop=/usr/bin/bash -c " \ /usr/sbin/ip rule del uidrange 1001-1001 table 200 || true; \ /usr/sbin/ip route flush table 200 || true; \ /usr/bin/wg set wg peer '#PEER_PUBLIC_KEY#' remove || true; \ /usr/sbin/ip link del dev wg || true; \ " [Install] WantedBy=multi-user.target
Thereās a bit to go through here, so Iāll take you through why it works. Most of it is just setting up WG to receive/send traffic. The bits that are relevant are:
/usr/sbin/ip rule add uidrange 1001-1001 table 200 || true; \ /usr/sbin/ip route add #VPN_ENDPOINT# via #ROUTER_IP# dev enp3s0 table 200 || true; \ /usr/sbin/ip route add 192.168.0.0/24 via 192.168.0.1 dev enp3s0 table 200 || true; \ /usr/sbin/ip route add #YOUR_STATIC_EXTERNAL_IP#/32 via #ROUTER_IP# dev enp3s0 table 200 || true; \ /usr/sbin/ip route add default via #WG_IPV4_ADDRESS# dev wg table 200 || true; \
ip rule add uidrange 1001-1001 table 200
adds a new rule where requests from UID 1001 go through table 200. A table is a subset of ip routing rules that are only relevant to certain traffic.ip route add #VPN_ENDPOINT# ...
ensures that traffic already going through the VPN - i.e. wireguard traffic - does. This is relevant for handshakes.ip route add 192.168.0.0/24 via 192.168.0.1 ...
is just excluding local traffic, as isip route add #YOUR_STATIC_EXTERNAL_IP
Finally, we add
ip route add default via #WG_IPV4_ADDRESS# ...
which routes all traffic that didnāt match any of the above rules (local traffic, wireguard) to go to the wireguard interface. From there, WG handles all the rest, and passes returning traffic back.Thereās going to be some individual tweaking here, but the long and short of it is, UID
1001
will have all their external traffic routed through WG. Any internal traffic between docker containers in a docker-compose should already be handled by podman pods and never reach the routing rules. Any traffic aimed at other services in the network - i.e.sonarr
callingsabnzbd
ortransmission
- will happen with a relevant local IP of the machine itās hosted on, and so will also be skipped. Localhost is already handled by existingip route
rules, so you shouldnāt have to worry about that either.Hopefully that helps - sorry if itās a bit confusing. I learned to set up my own IP routing to avoid
wg-quick
so that I could have greater control over the traffic flow, so this is quite a lot of my learning that Iām attempting to distill into one place.