Self-Hosted Runners
By default VeloGit runs each ritual on an ephemeral Fly.io machine. With the Docker dispatch provider it instead runs each ritual as an ephemeral container on a Docker daemon you control — so the whole platform, CI included, can run on a single VM you own.
This page is for operators self-hosting VeloGit.
How runs are dispatched
Section titled “How runs are dispatched”The rituals-dispatcher service decides where a run executes based on one
environment variable:
DISPATCH_PROVIDER | Runs each ritual as… |
|---|---|
fly (default) | a Fly.io machine |
gcp | a Cloud Run job |
docker | a container on the local Docker daemon |
Whichever provider is used, the model is the same: one fresh, single-use runner per run, built from the run’s base image (see Runner Environment). The runner binary is side-loaded from the data-plane at boot and authenticates with a per-run scoped token (see Service-to-Service Auth), so the Docker path is exactly as isolated, credential-wise, as the Fly path.
Requirements
Section titled “Requirements”- A host with a Docker daemon.
- The
rituals-dispatcherrunning on that host with:- the
dockerCLI available (it’s baked into the dispatcher image), and - the Docker socket mounted:
-v /var/run/docker.sock:/var/run/docker.sock.
- the
The dispatcher shells out to docker run against that socket to create one
container per run.
Configuration
Section titled “Configuration”Set these on the dispatcher:
| Variable | Required | Purpose |
|---|---|---|
DISPATCH_PROVIDER=docker | yes | Selects this provider. |
RUNNER_BINARY_URL | yes | Where the runner container downloads the rituals-service binary — the data-plane’s /internal/runner-binary. |
DATAPLANE_URL | yes | The data-plane base URL, as reachable from inside a runner container (see the gotcha below). |
DOCKER_NETWORK | no | Attaches each runner to a named Docker network so it can reach the data-plane by service name. |
NATS_URL, NATS_NKEY_SEED | as configured | Passed through to the runner. |
Secrets (VELO_RUN_TOKEN, NATS_NKEY_SEED) are passed to the container via a
temporary 0600 env-file, not -e flags, so they don’t appear in ps.
The one gotcha: DATAPLANE_URL must be reachable from the container
Section titled “The one gotcha: DATAPLANE_URL must be reachable from the container”This is the mistake to avoid. DATAPLANE_URL is used inside the runner
container to clone the repo and report results. http://localhost:8080 almost
never works there — localhost inside a container is the container itself, not
your host or the data-plane. Use one of:
- a shared Docker network + the data-plane’s service name
(
http://data-plane:8080) — setDOCKER_NETWORKto that network. This is the cleanest option and what the compose example below uses. http://host.docker.internal:8080(Docker Desktop; on Linux add--add-host=host.docker.internal:host-gateway), or- the host’s LAN/VPC IP.
docker-compose example
Section titled “docker-compose example”The runner reaches the data-plane by service name over a shared network:
services: data-plane: image: velogit/data-plane networks: [velo] # …storage, DB, etc.
rituals-dispatcher: image: velogit/rituals-dispatcher networks: [velo] environment: DISPATCH_PROVIDER: docker DATAPLANE_URL: http://data-plane:8080 RUNNER_BINARY_URL: http://data-plane:8080/internal/runner-binary DOCKER_NETWORK: velo # runners join this network too NATS_URL: nats://nats:4222 # NATS_NKEY_SEED: … volumes: - /var/run/docker.sock:/var/run/docker.sock # required
networks: velo:Each run then starts a container roughly equivalent to:
docker run -d --rm --env-file <tmp> --network velo \ --label velo.run=<run-id> <base-image> sh -c '<bootstrap>'Security
Section titled “Security”- Mounting the Docker socket grants the dispatcher root-equivalent control of the host. Run the dispatcher on a host dedicated to VeloGit, not a shared box.
- Runs execute as containers on the same host. That’s fine when you trust the code in your repos (the normal self-host case). It is not sufficient isolation for running untrusted code from multiple tenants on shared hardware — for that you want microVM isolation (e.g. Firecracker/Kata), which the Docker provider does not give you.
Known limitations (current)
Section titled “Known limitations (current)”- Cancellation is passive. Containers run with
--rmand honour the pipeline timeout, so they self-terminate and clean up — but cancelling a run marks it terminal in the UI without immediately killing the container; the container finishes its current work or times out first. - Single node. The dispatcher talks to its local Docker daemon only; there is no remote-Docker or multi-node scheduling yet.
- No dependency cache. Every run is a cold start (fresh clone,
packages:reinstall). Pre-baking abase_imageis the workaround — see Runner Environment.