anvilsign in

collin/anvil

main / compose.override.yaml
1# Local development only. `docker compose` merges this automatically when it
2# sits next to compose.yaml, and `hag` always passes `-f compose.yaml`
3# explicitly, so none of it reaches hagrid -- where Caddy fronts the container,
4# the SSO issuer is a public HTTPS URL with a normal CA, and there is
5# deliberately no Docker socket.
6#
7# The images compile themselves (docker/Dockerfile), so bringing the stack up
8# is one command -- PROFILE=debug below keeps the local loop off the optimizer,
9# and a BuildKit cache mount means an edit recompiles the crate you touched
10# rather than the whole workspace:
11#
12# docker compose up -d --build
13#
14# Then http://127.0.0.1:20640. `docker compose logs -f`, `docker compose down`.
15#
16# deploy/dev.sh remains the fuller path: it also generates deploy/dev-ca.crt
17# (mounted below), waits for /-/healthz, and points portless at the container.
18
19# Two CI runners, identical but for their names. anvil executes no jobs itself
20# (docs/remote-runners.md), so without these a queued run sits forever; two of
21# them rather than one is what makes concurrent pipelines and the "is any
22# runner connected" side of platform routing testable on one machine.
23#
24# LOCAL ONLY, and the reason is worth being explicit about: a containerized
25# runner needs the host's Docker socket, which is the root-equivalent hold that
26# moving CI off the forge removed. Real runners are native processes on their
27# own host (docs/remote-runners.md § Isolation on macOS). This file already
28# hands anvil the same socket for agent sessions, so the local trust boundary
29# is unchanged -- the deployed compose.yaml grants neither.
30x-runner: &runner
31 image: anvil-worker-dev:latest
32 build:
33 context: .
34 dockerfile: docker/Dockerfile
35 target: worker
36 args:
37 PROFILE: debug
38 platforms:
39 - linux/amd64
40 restart: unless-stopped
41 depends_on:
42 - anvil
43 environment: &runner-env
44 # Container-to-container over the hagrid network, by compose service name:
45 # ANVIL_BASE_URL is what browsers use and does not resolve in here.
46 ANVIL_URL: ${ANVIL_RUNNER_URL:-http://anvil:3000}
47 # Must match `[ci] runner_token` baked in from deploy/anvil.dev.toml.
48 ANVIL_RUNNER_TOKEN: ${ANVIL_RUNNER_TOKEN:-dev-runner-token}
49 RUST_LOG: ${ANVIL_RUNNER_LOG:-anvil_worker=info}
50 volumes:
51 # The runner is a Docker client: it creates each job's sandbox as a sibling
52 # container on this host's daemon. The JOB container still gets no socket
53 # and no mounts -- the checkout is uploaded and artifacts downloaded through
54 # the API (crates/anvil-worker/src/executor.rs).
55 #
56 # `label=disable` rather than a `:z` relabel, same as anvil above: :z would
57 # rewrite the SELinux label on the host's socket, which every other
58 # container on this machine also uses.
59 - /var/run/docker.sock:/var/run/docker.sock
60 group_add:
61 - "${DOCKER_GID:-970}"
62 security_opt:
63 - label=disable
64 networks:
65 - hagrid
66
67services:
68 anvil:
69 # A distinct tag, so a local build carrying the DEV config can never be
70 # mistaken for -- or pushed as -- the production image.
71 image: anvil-dev:latest
72 build:
73 args:
74 # Bakes deploy/anvil.dev.toml at /etc/anvil/anvil.toml instead of
75 # production's: local base_url, the login.localhost issuer, agent
76 # sessions on, and shorter periodic scans.
77 CONFIG: deploy/anvil.dev.toml
78 # Unoptimized: the local loop cares about how long a rebuild takes,
79 # not how fast the server runs. Must match the runners' PROFILE, or
80 # the two stages fall out of one cargo invocation into two.
81 PROFILE: debug
82 # Not `anvil`: that name belongs to deploy/dev.sh's container, and compose
83 # refuses to adopt a container it did not label.
84 container_name: anvil-dev
85
86 # !override, not a merge: `ports` is one of the keys compose CONCATENATES,
87 # so without it the production entry survives and the container tries to
88 # bind 165.232.162.167:22 on this machine.
89 ports: !override
90 # A stable, collision-resistant port for this project (`devport`), so it
91 # does not wander between runs.
92 - "127.0.0.1:${ANVIL_DEV_PORT:-20640}:3000"
93 # anvil.dev.toml advertises 20641 in SSH clone URLs; keep the two in step.
94 - "127.0.0.1:${ANVIL_DEV_SSH_PORT:-20641}:2222"
95
96 volumes: !override
97 # Separate from production's `anvil-data`, and the same volume dev.sh
98 # uses, so the two local paths share state. `docker volume rm
99 # anvil-dev-data` starts over.
100 - anvil-dev-data:/data
101
102 # Agent sessions (`[agent] enabled = true` in anvil.dev.toml) drive
103 # Docker directly, so they need the socket. CI does NOT -- that moved to
104 # anvil-worker, which is its own Docker client on its own machine.
105 #
106 # `label=disable` below rather than a `:z` relabel: :z would rewrite the
107 # SELinux label on the HOST's socket, which every other container on this
108 # machine also uses.
109 - /var/run/docker.sock:/var/run/docker.sock
110
111 # The SSO back channel calls https://login.localhost directly, and that
112 # certificate comes from the CA portless generated. anvild ships its own
113 # root store (rustls), so `portless trust` does not reach it -- hence a
114 # bundle of the host's roots plus that CA, which SSL_CERT_FILE points at.
115 # deploy/dev.sh writes this file; regenerate it by hand with:
116 # cat /etc/ssl/certs/ca-bundle.crt ~/.portless/ca.pem > deploy/dev-ca.crt
117 - ./deploy/dev-ca.crt:/etc/ssl/certs/anvil-dev-ca.crt:ro,z
118
119 # The gid owning /var/run/docker.sock on this host. `stat -c '%g'
120 # /var/run/docker.sock` if it differs on yours.
121 group_add:
122 - "${DOCKER_GID:-970}"
123 security_opt:
124 - label=disable
125
126 # Appended to production's host.docker.internal entry, not replacing it:
127 # login.localhost resolves to the container's own loopback otherwise,
128 # rather than the host's portless proxy.
129 extra_hosts:
130 - "login.localhost:host-gateway"
131
132 environment:
133 SSL_CERT_FILE: /etc/ssl/certs/anvil-dev-ca.crt
134 # Overrides anvil.dev.toml's baked base_url. Point it at
135 # http://127.0.0.1:20640 when testing websockets -- portless proxies
136 # them over HTTP/2, where they are currently broken -- but note that
137 # changing it also changes the OIDC redirect_uri, which the provider
138 # matches exactly.
139 ANVIL_BASE_URL: ${ANVIL_BASE_URL:-https://anvil.localhost}
140
141 # A third is four lines: copy one of these and bump the name. The names are
142 # what run headers and `[ci]` logs show, so keep them distinct -- an unnamed
143 # runner falls back to its hostname, which in a container is a hex id.
144 runner-1:
145 <<: *runner
146 container_name: anvil-runner-1
147 environment:
148 <<: *runner-env
149 ANVIL_RUNNER_NAME: dev-1
150
151 runner-2:
152 <<: *runner
153 container_name: anvil-runner-2
154 environment:
155 <<: *runner-env
156 ANVIL_RUNNER_NAME: dev-2
157
158volumes:
159 anvil-dev-data:
160 name: anvil-dev-data
161 # Same expected "not created by Docker Compose" warning as production's
162 # volume: dev.sh made this one first, and compose adopts it.