Containerize the app (Dockerfile, docker-compose.yaml) and add a CI deploy job that syncs the checkout to /srv/docker/maptool and runs docker compose directly, using the runner's new "host" execution label to reach the host's Docker daemon and filesystem.
125 lines
5.0 KiB
Plaintext
125 lines
5.0 KiB
Plaintext
= Deployment
|
|
:toc:
|
|
:toclevels: 2
|
|
|
|
MapTool deploys as a Docker container on the same server the Gitea Actions
|
|
runner runs on. The `deploy` job in `.gitea/workflows/ci.yml` runs directly
|
|
inside the runner's own container (see "The runner's host label" below) and
|
|
calls `docker compose` directly against the host's Docker daemon — no
|
|
systemd unit is involved for this app.
|
|
|
|
== Layout on the server
|
|
|
|
`/srv/docker/maptool/` holds:
|
|
|
|
* A checkout of this repository (kept in sync by the deploy job, see
|
|
below) — `Dockerfile`, `docker-compose.yaml`, application code.
|
|
* `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.
|
|
|
|
== The runner's "host" label
|
|
|
|
The Gitea Actions runner (`gitea/runner:3`) normally runs each job in a
|
|
fresh, isolated container spawned via its mounted
|
|
`/var/run/docker.sock` — that's how the `check` job runs (`runs-on:
|
|
ubuntu-latest`). Such a job container has no access to the host
|
|
filesystem or to a host `systemctl`, so it can't reach
|
|
`/srv/docker/maptool` or manage a systemd unit.
|
|
|
|
The `deploy` job instead uses `runs-on: host`, a label configured in
|
|
the runner's own `config.yaml` that makes the runner (`gitea-runner`,
|
|
the binary inside the `gitea/runner:3` image) execute the job's
|
|
steps directly inside the runner's *own* container, without spawning a
|
|
new one. That container already has `/var/run/docker.sock` mounted, so
|
|
`docker`/`docker compose` commands run there act directly on the
|
|
host's Docker daemon — this is the same runner used for `check`, just
|
|
with a second execution mode, not a separate runner.
|
|
|
|
For this to work, the runner's own `docker-compose.yaml` (the one that
|
|
starts the runner itself, not this app) needs one more bind mount so
|
|
`/srv/docker` inside the runner container is the *same* path as on the
|
|
real host:
|
|
|
|
[source,yaml]
|
|
----
|
|
services:
|
|
runner:
|
|
image: docker.io/gitea/runner:3
|
|
# ...
|
|
volumes:
|
|
- ./config.yaml:/config.yaml
|
|
- ./data:/data
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
- /srv/docker:/srv/docker
|
|
----
|
|
|
|
And the runner's `config.yaml` needs the `host` label added alongside
|
|
whatever docker-mode labels it already has. If `config.yaml` is empty
|
|
(no prior customization), generate a real default first rather than
|
|
hand-writing one — schemas vary across runner versions:
|
|
|
|
[source,shell]
|
|
----
|
|
docker exec <runner-container> gitea-runner config generate > config.yaml
|
|
----
|
|
|
|
(`config generate` — not `generate-config`, which is deprecated; and
|
|
the binary is `gitea-runner`, not `act_runner`.)
|
|
|
|
Then add the label to the generated `runner: labels:` list — the
|
|
generated defaults are commented out, and this list fully *replaces*
|
|
the runner's built-in defaults rather than adding to them, so all of
|
|
them need to be uncommented too, not just the new one:
|
|
|
|
[source,yaml]
|
|
----
|
|
runner:
|
|
labels:
|
|
- "host:host"
|
|
- "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest"
|
|
- "ubuntu-24.04:docker://docker.gitea.com/runner-images:ubuntu-24.04"
|
|
- "ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04"
|
|
----
|
|
|
|
The `1:1` path match matters: the deploy job's working directory
|
|
(`/srv/docker/maptool`) needs to resolve to the *same* path whether
|
|
read from inside the runner container or by the Docker daemon on the
|
|
real host, because `docker-compose.yaml`'s relative bind mount
|
|
(`./data`) gets resolved to an absolute path by the `docker compose`
|
|
CLI *before* being sent to the daemon — if that path only existed
|
|
inside the runner container, the daemon (running on the bare host)
|
|
would create/mount the wrong (or a nonexistent) directory.
|
|
|
|
After editing both files, recreate the runner container so it picks
|
|
up the new volume and label (`docker compose up -d` in the runner's
|
|
own directory).
|
|
|
|
== Puma's bind address
|
|
|
|
`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
|
|
(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.
|
|
|
|
== CI deploy job
|
|
|
|
`.gitea/workflows/ci.yml`'s `deploy` job runs after `check` passes, only
|
|
on a push to `main`, with `runs-on: host`:
|
|
|
|
. Copies the checkout into `/srv/docker/maptool/` using plain
|
|
`cp`/`rm` (no `rsync` in the runner image): 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 restart if nothing did.
|
|
|
|
No `sudo`, no systemd, no SSH — the runner container's existing access
|
|
to `/var/run/docker.sock` and (once added) `/srv/docker` is enough.
|