# Podman-based Gitea Actions runners Run two independent Gitea Actions runners on one host as rootless Podman containers managed by systemd (Quadlet) — one per project — instead of two VMs. Uses the **host executor**: workflow steps run directly inside a purpose-built runner image that already has the Rust toolchain and all build dependencies baked in, so CI runs skip the per-run install cost and no Docker/Podman socket is needed. ## Files | File | Purpose | |------|---------| | `Containerfile` | Runner image: Debian + build deps + clang + Rust + Node + `reuse` + `act_runner`. | | `entrypoint.sh` | Registers on first boot (if needed), then runs the daemon. | | `config.yaml` | act_runner config template (seeded into each runner's volume). | | `trx-rs-runner.container` | Quadlet unit for the trx-rs runner. | | `project2-runner.container` | Quadlet unit for the second project's runner. | ## Prerequisites (once per host) Rootless Podman with cgroups v2 (default on modern distros). As the unprivileged user that will own the runners: ```bash # Survive logout / start on boot without an interactive session. loginctl enable-linger "$USER" ``` No `podman.socket` is required for the host executor. ## 1. Build the image ```bash cd container podman build -t trx-rs-ci:latest . ``` ## 2. Get a registration token For **each** repo: *Settings → Actions → Runners → Create new Runner* and copy the token. (Org- or instance-level tokens work too if you prefer wider scope.) ## 3. Install and start the runners ```bash mkdir -p ~/.config/containers/systemd cp trx-rs-runner.container project2-runner.container ~/.config/containers/systemd/ # Paste each repo's token for the FIRST boot only: # Environment=GITEA_RUNNER_REGISTRATION_TOKEN=xxxx… $EDITOR ~/.config/containers/systemd/trx-rs-runner.container $EDITOR ~/.config/containers/systemd/project2-runner.container systemctl --user daemon-reload systemctl --user start trx-rs-runner systemctl --user start project2-runner systemctl --user status trx-rs-runner podman logs -f gitea-runner-trx-rs ``` Once each runner shows **online** in the repo's runner list, blank out the `GITEA_RUNNER_REGISTRATION_TOKEN` line again (the registration is persisted in the `…-data` volume) and `systemctl --user daemon-reload`. ## Required workflow change: the `reuse` job The host executor runs steps directly in the container and therefore **cannot run Docker-based actions**. The current `reuse` job uses `fsfe/reuse-action@v5`, which is a Docker action. `reuse` is baked into the image, so replace that job with a plain command: ```yaml reuse: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: REUSE compliance run: reuse lint ``` The `lint` and `test` jobs need no changes: their `sudo apt-get …` and rustup steps still run, but become fast no-ops because the image already has those packages and the toolchain. (`sudo` is included in the image for exactly this reason.) > If you would rather keep Docker-based actions and per-run images, use the > **Docker executor** instead: drop the `:host` suffix from the label in > `config.yaml`, enable `systemctl --user --now enable podman.socket`, mount it > into the container, and set `container.docker_host` to the socket path. That > trades the baked-in speed for stronger per-job isolation. ## Tuning - **`capacity`** (in `config.yaml`) — concurrent jobs per runner. Rust builds are heavy; 1–2 is sensible when two runners share a host. - **`PodmanArgs=--cpus/--memory`** (in each `.container`) — hard resource caps so one project cannot starve the other. - **SELinux** — the `:Z` volume flag is already set; keep it if SELinux is enforcing. ## Committing these files If you add this directory to a REUSE-checked repo, register the markdown in `REUSE.toml` (the other files carry inline SPDX headers): ```toml [[annotations]] path = ["container/**"] SPDX-FileCopyrightText = "2026 Stan Grams " SPDX-License-Identifier = "GPL-2.0-or-later" ```