anvilsign in

collin/anvil

main / compose.yaml
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# `hag deploy` is the whole deploy. docker/Dockerfile compiles anvild itself
7# from this build context, cross-linking to static x86_64-musl, so there is no
8# staging step in front of it and nothing that can be silently out of date.
9#
10# IMAGE_TAG picks which build runs. hag sets it to the git sha it just pushed,
11# so `hag status` names the exact build, and rolling back on the host is
12# `IMAGE_TAG=<sha> docker compose up -d --no-build`.
13services:
14 anvil:
15 image: registry.vibe.richardscollin.com/anvil:${IMAGE_TAG:-latest}
16 build:
17 # Context is the repo root -- the Dockerfile compiles the workspace out
18 # of it, and the baked config lives in deploy/ -- so it has to be named.
19 context: .
20 dockerfile: docker/Dockerfile
21 target: anvil
22 # hagrid is x86_64 and the binary is cross-compiled to x86_64-musl.
23 # The Dockerfile's runtime stages pin the same thing; saying it here too
24 # keeps an arm64 workstation from being asked to resolve an arm64 base.
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