anvilsign in

collin/anvil

1# anvil on hagrid, deployed with `hag` -- the shared deploy tool for every
2# project on that host (build, push to the private registry, then pull and
3# recreate there). Both this machine and hagrid need
4# `docker login registry.vibe.richardscollin.com` once.
5#
6# The image carries a PREBUILT binary: the Dockerfile only COPYs in
7# docker/anvil/anvild, which deploy/build.sh cross-compiles as a static
8# x86_64-musl executable. So `hag deploy` on its own is not enough -- use
9# ./deploy/deploy.sh, which stages the binary and then calls it.
10#
11# IMAGE_TAG picks which build runs. hag sets it to the git sha it just pushed,
12# so `hag status` names the exact build, and rolling back on the host is
13# `IMAGE_TAG=<sha> docker compose up -d --no-build`.
14services:
15 anvil:
16 image: registry.vibe.richardscollin.com/anvil:${IMAGE_TAG:-latest}
17 build:
18 # Context is the repo root (the binary is staged under docker/, and the
19 # baked config lives in deploy/), so the Dockerfile has to be named.
20 context: .
21 dockerfile: docker/anvil/Dockerfile
22 # hagrid is x86_64 and the staged binary is x86_64-musl. Pinning the
23 # platform stops an arm64 workstation from producing an image whose
24 # base layers the host cannot run.
25 platforms:
26 - linux/amd64
27 container_name: anvil
28 restart: unless-stopped
29
30 # Host config that must not be baked into the image -- today that is
31 # ANVIL_OIDC_CLIENT_SECRET (docs/oidc.md). Optional so the container still
32 # starts where there is none, matching how run.sh only passed the secret
33 # when it found one: an empty value here would override the baked config
34 # and turn a confidential OIDC client into a public one.
35 env_file:
36 - path: .env
37 required: false
38
39 # [ci] deploy_webhook posts to a receiver on the host, so the container
40 # needs a route to it. Docker Desktop supplies this name; Linux does not.
41 extra_hosts:
42 - "host.docker.internal:host-gateway"
43
44 ports:
45 # Git-over-SSH only. The web port stays unpublished -- Caddy reaches
46 # anvil:3000 over the hagrid network and terminates TLS.
47 #
48 # Published on the droplet's DEFAULT public IPv4 alone. The reserved IP
49 # (137.184.249.48) arrives on the anchor address 10.15.0.6, where the
50 # host's own sshd listens, so binding one specific IP here keeps the two
51 # off each other.
52 - "${ANVIL_SSH_BIND_IP:-165.232.162.167}:${ANVIL_SSH_PORT:-22}:2222"
53
54 # NO DOCKER SOCKET. anvil does not execute CI any more -- runners dial in
55 # and run jobs on their own daemons (docs/remote-runners.md), so the
56 # container has no reason to reach Docker. Leaving the mount out is what
57 # removes the root-equivalent hold the internet-facing process used to have
58 # on the host. Agent sessions (crates/anvil-agent) are the one thing this
59 # gives up; they are off in deploy/anvil.toml and off by default. Read
60 # docs/untrusted-mode.md before putting the mount back.
61 volumes:
62 - anvil-data:/data
63
64 networks:
65 - hagrid
66
67volumes:
68 # `name:` pins the volume to the exact name the pre-compose deploys used, so
69 # this adopts the existing SQLite DB, bare repos and SSH host key rather than
70 # coming up against an empty `anvil_anvil-data` that compose would otherwise
71 # derive from the project name. A named volume (not a bind mount) keeps it
72 # owned by the in-container `anvil` user.
73 anvil-data:
74 name: anvil-data
75 # (Compose warns that it did not create this volume, then adopts it. That
76 # is the intended path off the `docker run` deploys; `external: true`
77 # would silence it but break a first install on a fresh host.)
78
79# Caddy runs on this network and reverse-proxies to the container by name.
80# The hagrid repo owns the network's lifecycle.
81networks:
82 hagrid:
83 external: true