Drive maptool's deploy through the podman-compose@ systemd service

The service's own ExecStart deliberately runs a plain podman compose
up (no --build) since ExecStartPre already tears down the previous
containers/networks/volumes on every start — but that leaves the
built image alone, so the deploy job now removes it directly first
(what forces a fresh build) and drives start/stop through systemctl
rather than calling podman/docker compose by hand. Also switches
/srv/docker/maptool to /srv/podman/maptool per the symlink now in
place, and updates deployment.adoc to match.
This commit is contained in:
randy committed 2026-09-08 20:33:03 +02:00
1 parent 0c4ee97368
commit e4764c64e4
2 files changed
+59 -29

No files matched your search

+38 -19
View File
@@ -2,9 +2,10 @@
:toc:
:toclevels: 2
MapTool deploys as a Docker container on the same server the Gitea Actions
runners run on. Two separate runners are registered against the Gitea
instance:
MapTool deploys as a Podman container (via `podman compose`, driven by a
`podman-compose@.service` systemd template — see below) on the same server
the Gitea Actions runners run on. Two separate runners are registered
against the Gitea instance:
* The existing Docker-based runner (`gitea/runner:3`), labels
`ubuntu-latest`/`ubuntu-24.04`/`ubuntu-22.04` — runs the `check` job
@@ -24,14 +25,19 @@ whatever's installed on the server.
== Layout on the server
`/srv/docker/maptool/` holds:
`/srv/podman/maptool/` (`/srv/podman` is a symlink to `/srv/docker`, a
leftover from before the Docker → Podman migration — kept around so
existing paths/tooling that still assume `/srv/docker` keep working;
expect this to flip the other way, `/srv/docker` symlinked to
`/srv/podman`, once nothing depends on the old name anymore) holds:
* A checkout of this repository (kept in sync by the deploy job, see
below) — `Dockerfile`, `docker-compose.yaml`, application code.
`podman compose` reads the same `docker-compose.yaml` — no
Podman-specific compose file needed.
* `data/`, *not* part of that sync — uploaded GPX files and
`offline.pmtiles`. Persisted via a bind mount (`./data:/app/data` in
`docker-compose.yaml`), so a redeploy (`docker compose up --build`)
never touches it.
`docker-compose.yaml`), so a redeploy never touches it.
== The native deploy runner
@@ -40,8 +46,11 @@ https://docs.gitea.com/runner/installation/binary/), running as a
systemd service (`gitea-runner.service`) under a dedicated
`gitea-runner` system user, which needs:
* Docker access (member of the `docker` group).
* Write access to `/srv/docker/maptool`.
* Passwordless `sudo` (confirmed via `sudo -l -U gitea-runner`) —
`podman-compose@maptool.service` is a root-owned systemd unit (see
below), and the image it builds lives in root's Podman storage, so
starting/stopping it and removing its image both require root.
* Write access to `/srv/podman/maptool`.
Its `config.yaml` (`/etc/gitea-runner/config.yaml`) declares a single
label:
@@ -61,20 +70,22 @@ purpose deploy runner needs.
`config/puma.rb` binds to `127.0.0.1` by default (see
`docs/architecture.adoc`) — fine for local dev, but inside a container
that would make the app unreachable through Docker's published port
that would make the app unreachable through the published port
(loopback is scoped to the container's own network namespace).
`docker-compose.yaml` sets `BIND_HOST=0.0.0.0` to override that for
the deployed container specifically; Docker's own port publishing
(`ports:` in the compose file) is the network boundary there instead
of Puma's bind address.
`docker-compose.yaml` sets `BIND_HOST="[::]"` to override that for the
deployed container specifically — the IPv6 wildcard, which (dual-stack
being the Linux default) also accepts IPv4 connections, covering both
with a single listener; the compose file's own port publishing
(`ports:`) is the network boundary there instead of Puma's bind
address.
== Secrets (`.env`)
`docker-compose.yaml` reads `SESSION_SECRET`, `GITEA_OAUTH_CLIENT_ID`,
`GITEA_OAUTH_CLIENT_SECRET` and `MAPTOOL_ALLOWED_GITEA_USER` from the
environment (see `docs/architecture.adoc` for what they're for) —
docker compose loads these automatically from a `.env` file next to
`docker-compose.yaml`, i.e. `/srv/docker/maptool/.env`. That file is
`podman compose` loads these automatically from a `.env` file next to
`docker-compose.yaml`, i.e. `/srv/podman/maptool/.env`. That file is
gitignored and dockerignored on purpose: nothing here ever puts
secrets in the repository or a built image layer.
@@ -208,9 +219,17 @@ on a push to `main`, with `runs-on: cube-host`:
`actions/checkout@v4` — a leftover from when this job ran in a
Node-less container; kept as-is since there's no reason to bring
Node back in for something this already does.
. Copies the checkout into `/srv/docker/maptool/` using plain `cp`/`rm`:
. Copies the checkout into `/srv/podman/maptool/` using plain `cp`/`rm`:
clears everything in the target except `data/`, copies the fresh
checkout over, then drops the copied `.git`.
. `cd /srv/docker/maptool && docker compose -p maptool up --build -d`
— rebuilds the image and recreates the container if anything
changed; a no-op if nothing did.
. Stops `podman-compose@maptool.service`, force-removes the
`maptool:latest` image directly via `podman rmi`, then starts the
service again. The service (`/etc/systemd/system/podman-compose@.service`,
a systemd template instantiated as `podman-compose@maptool.service`)
owns the actual container lifecycle — its `ExecStartPre` tears down
the previous containers/networks/volumes on every start, and its
`ExecStart` runs a plain `podman compose up` (deliberately no
`--build`), which only builds when the image tag isn't already
present locally. Removing the tag first is what forces a fresh build
from the code just synced above; without it, a restart alone would
just relaunch the previous image unchanged.