diff --git a/src/content/unix/docker-compose-to-podman-quadlet.md b/src/content/unix/docker-compose-to-podman-quadlet.md new file mode 100644 index 0000000..077c5a3 --- /dev/null +++ b/src/content/unix/docker-compose-to-podman-quadlet.md @@ -0,0 +1,215 @@ +--- +title: 'Docker -> Podman + Quadlet' +description: 'Moving from Docker Compose to Podman with Quadlet' +updateDate: 'Nov 17 2024' +--- + +Containers are light-weight virtual machines, often used to run isolated +services on a server. They (typically) use the host kernel and don't virtualise +resources, leading to higher performance with minimal overhead compared to +traditional virtual machines (like qemu). + +[Docker](https://en.wikipedia.org/wiki/Docker_(software)) is the most widely +used container and has been at the head of the industry for a while. It uses a +daemon run by the root user to start its containers. This has long been a +security concern, as a malicious entity that [breaks out of the container]( +https://pwning.systems/posts/escaping-containers-for-fun/) will have root access +to the system! + +[Podman](https://www.redhat.com/en/topics/containers/what-is-podman), a tool +developed by Redhat, is quite similar to docker but crucially doesn't require a +daemon to run. This means unprivileged users can still get the benefits of +containerization! Docker has recently been experimenting with a similar +["rootless mode"](https://docs.docker.com/engine/security/rootless/), but it's +not nearly at feature parity. Further, since Podman is developed by Redhat, it +integrates with other Redhat projects very nicely, like systemd, +[nftables](https://wiki.archlinux.org/title/Nftables), and +[cockpit](https://wiki.archlinux.org/title/Cockpit). + +## Summary + +In this blog, we'll look at migrating an existing Docker Compose service to +Podman! We'll cover setting up our new Podman service with systemd's Quadlet +integration with a new unprivileged user. + +Prerequisites: + - A computer with linux (root access required, if you want to make a new user) + +Optional: + - Root access, if you'd like to make a new user + - An existing Docker Compose service. You can use the one in this tutorial too + +I'm using archlinux for this example, but any systemd-based linux will work +(Ubuntu, Debian, Fedora, Redhat...). You might need to change a few of the +user-creating commands on other systems. + +## New User + +We will assume 2 users: + - `emily` is an existing sudoer who's running a Docker Compose service + - `kate` will be a new unprivileged user who will run our Podman Quadlet + +Start by logging into `root` and setting up `kate` (optional if you want to use +`emily` to host your Quadlet): + +```bash +sudo su - +useradd --create-home --shell /bin/bash kate +passwd kate # Set some sort of password +``` + +For Podman, we'll need to give `kate` a range of sub-ids, which the container +can use to differentiate users, while still all being `kate`: + +```bash +cat /etc/subuid +cat /etc/subgid +# Based on output of above, find a range of 65536 ids which aren't overlapping +# with another user. For example, here we use 30000 +usermod --add-subuids 70000-135536 --add-subgids 70000-135536 kate +``` + +Assuming we don't typically use the `kate` user, we also want to make sure our +containers aren't killed once we logout of `kate`: + +```bash +loginctl enable-linger kate +``` + +Podman is also quite sensitive to XDG environment variables. Make sure you have +them setup properly. For example: + +```bash +cat <> /home/kate/.bashrc +export XDG_CONFIG_HOME=~/.config +export XDG_CACHE_HOME=~/.cache +export XDG_DATA_HOME=~/.local/share +export XDG_STATE_HOME=~/.local/state +export XDG_DATA_DIRS='/usr/local/share:/usr/share' +export XDG_CONFIG_DIRS='/etc/xdg' +export XDG_RUNTIME_DIR="/run/user/$(id -u kate)" +FILE +chown kate:kate /home/kate/.bashrc +``` + +Now to make sure that worked: + +```bash +su -l kate +podman ps # This shouldn't give any warnings, just a blank table +podman info | grep rootless # This should give a line like "rootless: true" +``` + +## Migrating Docker Compose to Podman + Quadlet + +Consider this docker-compose.yml: + +```yaml +services: + open-webui: + image: ghcr.io/open-webui/open-webui:v0.3.35 + restart: unless-stopped + environment: + OPENAI_API_BASE_URLS: https://api.mistral.ai/v1 + OPENAI_API_KEYS: + ports: + - "9130:8080" + volumes: + - type: bind + source: ./data + target: /app/backend/data +``` + +We could covert this to a simple Podman bash script. It's a good idea to try +this step as `kate` before proceeding. Notice that `kate` will need her own +`./data` directory. Try running this as `kate`: + +```bash +#!/usr/bin/env bash +podman run \ + --rm \ + --name open-webui \ + -e OPENAI_API_BASE_URLS="https://api.mistral.ai/v1" \ + -e OPENAI_API_KEYS="" \ + -p 9130:8080 \ + -v ./data:/app/backend/data \ + ghcr.io/open-webui/open-webui:v0.3.35 +``` + +A Quadlet file is similar to a systemd unit file, but describes the same things +as a docker-compose.yml: + +```ini +[Unit] +Description=Open WebUI container +Wants=network-online.target +After=network-online.target +After=local-fs.target + +[Container] +ContainerName=open-webui +Image=ghcr.io/open-webui/open-webui:v0.3.35 + +Environment=OPENAI_API_BASE_URLS="https://api.mistral.ai/v1" +Environment=OPENAI_API_KEYS="" + +PublishPort=9130:8080/tcp + +Volume=/home/kate/Documents/servers/openwebui/data:/app/backend/data + +[Service] +Restart=on-failure +TimeoutStartSec=900 + +[Install] +WantedBy=default.target +``` + +In this case, I put my Quadlet file at +`/home/kate/Documents/servers/openwebui/openwebui.container`. I put a +corresponding `/home/kate/Documents/servers/openwebui/data` directory to mount +in the container. + +See [Erick Patrick's repository](https://github.com/fpatrick/podman-quadlet) for +a great Quadlet template with all the important options! + +Quadlet files for a user should be at `~/.config/containers/systemd/`. To be a +bit more organized, we'll simply symlink our container out of +`~/Documents/servers` to here: + +```bash +ln -s /home/kate/Documents/servers/openwebui/openwebui.container /home/kate/.config/containers/systemd/ +``` + +## Running Quadlets + +Use `kate` to test if our Quadlet is working: + +``` +/usr/lib/podman/quadlet -dryrun -user +``` + +This should print out a file it calls `openwebui.service`. It looks similar to +our `openwebui.container`, but added more to the `[Service]` section and added a +`[X-Container]` section. Quadlet essentially converts our `.container` files to +systemd `.service` files, so that systemd can run them normally. Let's try it! + +Refresh your daemon: + +```bash +systemctl --user daemon-reload +``` + +Now check the status: + +```bash +systemctl --user status openwebui.service +``` + +We can start it just like any other systemd service: + +```bash +systemctl --user enable --now openwebui.service +``` + +Then, the analog to `docker log` is `journalctl --user -fu openwebui.service`!