# Architecture ## Design summary The runner is a Docker image that wraps the official `act_runner` binary in a controlled, predictable environment. All dependencies are installed at image build time. The running container is long-lived and operator-managed. --- ## Layers ``` ┌─────────────────────────────────────────────┐ │ Gitea instance (git.alwisp.com) │ ← dispatches jobs via act protocol └───────────────────┬─────────────────────────┘ │ HTTP / WebSocket ┌───────────────────▼─────────────────────────┐ │ custom-gitea-runner container │ │ ┌──────────────────────────────────────┐ │ │ │ tini (PID 1) │ │ │ │ └── docker-entrypoint.sh │ │ │ │ ├── validate binaries │ │ │ │ ├── install custom CAs │ │ │ │ ├── register (first boot only) │ │ │ │ └── act_runner daemon │ │ │ └──────────────────────────────────────┘ │ │ │ │ Bundled tools (installed at build time): │ │ bash, curl, git, jq, node, npm, │ │ docker CLI, ca-certificates, tini │ └───────────────────┬─────────────────────────┘ │ /var/run/docker.sock mount ┌───────────────────▼─────────────────────────┐ │ Host Docker daemon │ ← runs job containers └─────────────────────────────────────────────┘ ``` --- ## Key design decisions See `docs/decisions/` for ADR-style records of each major choice. | Decision | Choice | Doc | |---|---|---| | Base image | Ubuntu 22.04 | `decisions/0001-base-image.md` | | act_runner strategy | Wrap official binary | — | | Docker access | Socket mount (`/var/run/docker.sock`) | — | | PID 1 | `tini` | — | | Node.js source | NodeSource (Node 20 LTS) | — | | v1 user model | root (Docker socket requires it) | — | --- ## Registration modes **Mode A — Pre-generated config (preferred for stable production)** Operator mounts a pre-generated `runner.yaml` at `RUNNER_CONFIG_PATH`. The entrypoint detects the file and skips registration, going straight to daemon start. **Mode B — Bootstrap registration (first boot)** If `RUNNER_CONFIG_PATH` does not exist, the entrypoint reads `GITEA_INSTANCE_URL`, `GITEA_RUNNER_TOKEN`, and `GITEA_RUNNER_NAME` from the environment, generates a default config, and calls `act_runner register`. Subsequent starts use the persisted config (Mode A path). This flow is idempotent — reboots with an existing config will not re-register. --- ## Docker socket model The runner container mounts the host Docker socket (`/var/run/docker.sock`). This allows: - `docker build` and `docker push` in workflow steps - `docker/login-action` against external registries **Trust implication:** a workflow running on this runner has full access to the host Docker daemon. This is acceptable for a trusted, self-hosted environment. Future hardening options (DinD, rootless, network isolation) are tracked in `ROADMAP.md` Phase 6. --- ## Future hardening tracks - Rootless container execution - Docker-in-Docker (DinD) variant for true isolation - Dedicated BuildKit daemon - Ephemeral single-job runners - Network policy enforcement