anvilsign in

collin/anvil

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 carry prebuilt binaries, so stage them first (--debug compiles in
8# a fraction of the time a release build takes; --worker adds anvil-worker for
9# the two runner containers below):
10#
11# ./deploy/build.sh --debug --worker
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/worker/Dockerfile
35 platforms:
36 - linux/amd64
37 restart: unless-stopped
38 depends_on:
39 - anvil
40 environment: &runner-env
41 # Container-to-container over the hagrid network, by compose service name:
42 # ANVIL_BASE_URL is what browsers use and does not resolve in here.
43 ANVIL_URL: ${ANVIL_RUNNER_URL:-http://anvil:3000}
44 # Must match `[ci] runner_token` baked in from deploy/anvil.dev.toml.
45 ANVIL_RUNNER_TOKEN: ${ANVIL_RUNNER_TOKEN:-dev-runner-token}
46 RUST_LOG: ${ANVIL_RUNNER_LOG:-anvil_worker=info}
47 volumes:
48 # The runner is a Docker client: it creates each job's sandbox as a sibling
49 # container on this host's daemon. The JOB container still gets no socket
50 # and no mounts -- the checkout is uploaded and artifacts downloaded through
51 # the API (crates/anvil-worker/src/executor.rs).
52 #
53 # `label=disable` rather than a `:z` relabel, same as anvil above: :z would
54 # rewrite the SELinux label on the host's socket, which every other
55 # container on this machine also uses.
56 - /var/run/docker.sock:/var/run/docker.sock
57 group_add:
58 - "${DOCKER_GID:-970}"
59 security_opt:
60 - label=disable
61 networks:
62 - hagrid
63
64services:
65 anvil:
66 # A distinct tag, so a local build carrying the DEV config can never be
67 # mistaken for -- or pushed as -- the production image.
68 image: anvil-dev:latest
69 build:
70 args:
71 # Bakes deploy/anvil.dev.toml at /etc/anvil/anvil.toml instead of
72 # production's: local base_url, the login.localhost issuer, agent
73 # sessions on, and shorter periodic scans.
74 CONFIG: deploy/anvil.dev.toml
75 # Not `anvil`: that name belongs to deploy/dev.sh's container, and compose
76 # refuses to adopt a container it did not label.
77 container_name: anvil-dev
78
79 # !override, not a merge: `ports` is one of the keys compose CONCATENATES,
80 # so without it the production entry survives and the container tries to
81 # bind 165.232.162.167:22 on this machine.
82 ports: !override
83 # A stable, collision-resistant port for this project (`devport`), so it
84 # does not wander between runs.
85 - "127.0.0.1:${ANVIL_DEV_PORT:-20640}:3000"
86 # anvil.dev.toml advertises 20641 in SSH clone URLs; keep the two in step.
87 - "127.0.0.1:${ANVIL_DEV_SSH_PORT:-20641}:2222"
88
89 volumes: !override
90 # Separate from production's `anvil-data`, and the same volume dev.sh
91 # uses, so the two local paths share state. `docker volume rm
92 # anvil-dev-data` starts over.
93 - anvil-dev-data:/data
94
95 # Agent sessions (`[agent] enabled = true` in anvil.dev.toml) drive
96 # Docker directly, so they need the socket. CI does NOT -- that moved to
97 # anvil-worker, which is its own Docker client on its own machine.
98 #
99 # `label=disable` below rather than a `:z` relabel: :z would rewrite the
100 # SELinux label on the HOST's socket, which every other container on this
101 # machine also uses.
102 - /var/run/docker.sock:/var/run/docker.sock
103
104 # The SSO back channel calls https://login.localhost directly, and that
105 # certificate comes from the CA portless generated. anvild ships its own
106 # root store (rustls), so `portless trust` does not reach it -- hence a
107 # bundle of the host's roots plus that CA, which SSL_CERT_FILE points at.
108 # deploy/dev.sh writes this file; regenerate it by hand with:
109 # cat /etc/ssl/certs/ca-bundle.crt ~/.portless/ca.pem > deploy/dev-ca.crt
110 - ./deploy/dev-ca.crt:/etc/ssl/certs/anvil-dev-ca.crt:ro,z
111
112 # The gid owning /var/run/docker.sock on this host. `stat -c '%g'
113 # /var/run/docker.sock` if it differs on yours.
114 group_add:
115 - "${DOCKER_GID:-970}"
116 security_opt:
117 - label=disable
118
119 # Appended to production's host.docker.internal entry, not replacing it:
120 # login.localhost resolves to the container's own loopback otherwise,
121 # rather than the host's portless proxy.
122 extra_hosts:
123 - "login.localhost:host-gateway"
124
125 environment:
126 SSL_CERT_FILE: /etc/ssl/certs/anvil-dev-ca.crt
127 # Overrides anvil.dev.toml's baked base_url. Point it at
128 # http://127.0.0.1:20640 when testing websockets -- portless proxies
129 # them over HTTP/2, where they are currently broken -- but note that
130 # changing it also changes the OIDC redirect_uri, which the provider
131 # matches exactly.
132 ANVIL_BASE_URL: ${ANVIL_BASE_URL:-https://anvil.localhost}
133
134 # A third is four lines: copy one of these and bump the name. The names are
135 # what run headers and `[ci]` logs show, so keep them distinct -- an unnamed
136 # runner falls back to its hostname, which in a container is a hex id.
137 runner-1:
138 <<: *runner
139 container_name: anvil-runner-1
140 environment:
141 <<: *runner-env
142 ANVIL_RUNNER_NAME: dev-1
143
144 runner-2:
145 <<: *runner
146 container_name: anvil-runner-2
147 environment:
148 <<: *runner-env
149 ANVIL_RUNNER_NAME: dev-2
150
151volumes:
152 anvil-dev-data:
153 name: anvil-dev-data
154 # Same expected "not created by Docker Compose" warning as production's
155 # volume: dev.sh made this one first, and compose adopts it.