= Deployment :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: * The existing Docker-based runner (`gitea/runner:3`), labels `ubuntu-latest`/`ubuntu-24.04`/`ubuntu-22.04` — runs the `check` job in a fresh, isolated container per job, same as before. * A dedicated native runner, installed directly on the host (no Docker around it), label `deploy:host` — runs only the `deploy` job. They're separate because the Docker-based runner's jobs (whether in its usual isolated per-job containers, or tried directly inside its own container via a `host`-scheme label) have no reliable access to the host's filesystem, `systemctl`, or even a full toolset (its own container image is missing `rsync`, `docker` CLI, Node.js — it's built for spawning per-job containers via its `docker.sock` mount, not for running arbitrary host commands). The native runner sidesteps all of that: it *is* a host process, so `docker`, `git`, etc. are just whatever's installed on the server. == 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 native deploy runner Installed from the official binary (see 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`. Its `config.yaml` (`/etc/gitea-runner/config.yaml`) declares a single label: [source,yaml] ---- runner: labels: - "deploy:host" ---- The `host` scheme means jobs run directly via the runner process's own shell — no container spawned — which is exactly what a plain, single purpose deploy runner needs. == 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: deploy`: . Checks out the repo with plain `git` (`fetch` + `checkout`, authenticated via a one-off `http.extraHeader`) rather than `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`: 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.