= 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. == 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 gitignored and dockerignored on purpose: nothing here ever puts secrets in the repository or a built image layer. It's not created by hand on the server — the deploy job's "Write .env" step regenerates it on every deploy from repo-level Gitea Actions secrets (*Settings → Actions → Secrets*): `SESSION_SECRET`, `OAUTH_CLIENT_ID`, `OAUTH_CLIENT_SECRET`, `MAPTOOL_ALLOWED_GITEA_USER`. The OAuth2 ones aren't named `GITEA_OAUTH_CLIENT_*` to match the `.env` var names 1:1 — Gitea rejects secret names starting with `GITEA_` or `GITHUB_` as reserved — but the workflow step maps them to the right `.env` var names regardless. Gitea masks secret values in the job log automatically. Set these once in the repo settings and every deploy since keeps `.env` in sync — no server-side step needed beyond the first-time OAuth2 application registration below. `SESSION_SECRET` must stay the same value across deploys (regenerating it every push would silently log everyone out every time `main` changes) — generate it once (`openssl rand -hex 32`) and store that same value as the secret from then on, don't regenerate per-deploy. Register the OAuth2 application on the Gitea side (as the `MAPTOOL_ALLOWED_GITEA_USER` account): *Settings → Applications → Manage OAuth2 Applications → Create a new OAuth2 Application*, with redirect URI `https://map.priesol.net/auth/callback`. That gives you the client id/secret to store as the two `GITEA_OAUTH_*` secrets above. == 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.