Compare commits

..
2 Commits
Author SHA1 Message Date
randy 049d54e44e Use the short maptool:latest image name in the deploy job
CI / deploy (push) Successful in 4s
CI / check (push) Successful in 43s
docker.io/library/maptool:latest and maptool:latest resolve to the
same local image (verified on cube.local) - the docker.io/library
prefix is just podman's normalization of the unqualified name, not an
actual registry reference. Short form matches what docker-compose.yaml
itself uses and avoids implying a Docker Hub push/pull that never
happens.
2026-09-08 20:38:14 +02:00
randy e4764c64e4 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.
2026-09-08 20:33:03 +02:00
2 changed files with 62 additions and 29 deletions

No files matched your search

+24 -10
View File
@@ -35,7 +35,7 @@ jobs:
# Handled by a separate, native (non-Docker) runner registered with the
# single "cube-host" label (see docs/deployment.adoc) — it runs directly
# on the server, so it has docker/git/etc. from the host, no socket/bind
# on the server, so it has podman/git/etc. from the host, no socket/bind
# mount tricks needed. The Docker-based runner keeps handling `check`.
deploy:
needs: check
@@ -68,15 +68,15 @@ jobs:
# alone too — the glob below doesn't match them — and neither is ever
# part of the git checkout in the first place, since both are
# gitignored.
- name: Sync to /srv/docker/maptool
- name: Sync to /srv/podman/maptool
run: |
mkdir -p /srv/docker/maptool
for f in /srv/docker/maptool/*; do
mkdir -p /srv/podman/maptool
for f in /srv/podman/maptool/*; do
[ "$(basename "$f")" = "data" ] && continue
rm -rf "$f"
done
cp -a . /srv/docker/maptool/
rm -rf /srv/docker/maptool/.git
cp -a . /srv/podman/maptool/
rm -rf /srv/podman/maptool/.git
# Regenerated on every deploy from repo secrets (Settings -> Actions
# -> Secrets) rather than created once by hand on the server — see
@@ -88,16 +88,30 @@ jobs:
# actually reads.
- name: Write .env
run: |
cat > /srv/docker/maptool/.env <<EOF
cat > /srv/podman/maptool/.env <<EOF
SESSION_SECRET=${{ secrets.SESSION_SECRET }}
GITEA_OAUTH_CLIENT_ID=${{ secrets.OAUTH_CLIENT_ID }}
GITEA_OAUTH_CLIENT_SECRET=${{ secrets.OAUTH_CLIENT_SECRET }}
MAPTOOL_ALLOWED_GITEA_USER=${{ secrets.MAPTOOL_ALLOWED_GITEA_USER }}
LIVETRACK_WEBHOOK_SECRET=${{ secrets.LIVETRACK_WEBHOOK_SECRET }}
EOF
chmod 600 /srv/docker/maptool/.env
chmod 600 /srv/podman/maptool/.env
# podman-compose@maptool.service (/etc/systemd/system/podman-compose@.service)
# owns the container lifecycle — its ExecStartPre already tears down
# the previous containers/networks/volumes on every start, but not
# the built image, and its ExecStart runs a plain `podman compose up`
# (deliberately no `--build`), which only builds when the image tag
# isn't already present locally. So a bare restart would just
# relaunch the previous image; removing that tag first forces a
# fresh build from the code just synced above — purely local, no
# registry involved (same `maptool:latest` docker-compose.yaml
# already builds/tags; podman rmi never talks to a registry for a
# locally-built image). sudo is needed since the unit — and thus the
# image — is root's, not gitea-runner's own (gitea-runner has
# passwordless sudo for this on cube-host).
- name: Deploy
run: |
cd /srv/docker/maptool
docker compose -p maptool up --build -d
sudo systemctl stop podman-compose@maptool.service
sudo podman rmi -f maptool:latest || true
sudo systemctl start podman-compose@maptool.service
+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.