anvilsign in

collin/anvil · 67ea942a

Add Docker build and hagrid deployment

Collin Richards · 2026-06-09 18:29 UTC · 67ea942a2c65c09e0db51ba895c7e18443306af7 · parent 2b6114fa · browse files

added.dockerignore+7 −0
1+/target
2+/data
3+*.db
4+*.db-wal
5+*.db-shm
6+anvil.toml
7+.git
addedDEPLOY.md+118 −0
1+# Deploying anvil on hagrid
2+
3+anvil runs as a single Docker container at `anvil.richardscollin.com`, fronted by
4+hagrid's Caddy reverse proxy.
5+
6+- **Web** — anvil listens on `:3000` *inside* the container. Caddy (on the
7+ `hagrid` Docker network) reverse-proxies to it and provides HTTPS via Let's
8+ Encrypt. The web port is **not** published to the host.
9+- **SSH** — Caddy only fronts HTTP(S), so anvil's SSH server is **published
10+ directly to the host** on `:2222`. Git-over-SSH connects to
11+ `anvil.richardscollin.com:2222`.
12+- **Runtime** — fully self-contained: SQLite and the SSH crypto are compiled in,
13+ and gix is pure-Rust. No git, OpenSSH, or system sqlite in the image.
14+
15+## 1. DNS
16+
17+Add an `A`/`AAAA` (or `CNAME` to hagrid) record:
18+
19+```
20+anvil.richardscollin.com -> <hagrid's public IP>
21+```
22+
23+This one record covers both the web (443, via Caddy) and SSH (2222, direct to
24+the host).
25+
26+## 2. Caddy + index (in the hagrid repo)
27+
28+In `~/Code/hagrid/Caddyfile`, add:
29+
30+```
31+anvil.richardscollin.com {
32+ reverse_proxy anvil:3000
33+}
34+```
35+
36+In `~/Code/hagrid/sites.yaml`, add an entry:
37+
38+```yaml
39+- name: anvil
40+ host: anvil.richardscollin.com
41+```
42+
43+Then reload Caddy (`./hagrid.sh reload`, or `./hagrid.sh deploy` to push to the
44+host). Caddy resolves `anvil:3000` by container name over the `hagrid` network,
45+so the anvil container must join that network (the run script does this).
46+
47+## 3. Build & run the container
48+
49+On the hagrid host, from a checkout of this repo:
50+
51+```sh
52+./deploy/run.sh
53+```
54+
55+That builds `anvil:latest` and runs:
56+
57+```sh
58+docker run -d --name anvil --network hagrid --restart unless-stopped \
59+ -p 2222:2222 \
60+ -v anvil-data:/data \
61+ anvil:latest
62+```
63+
64+- `--network hagrid` — so Caddy can reach `anvil:3000`.
65+- `-p 2222:2222` — publishes SSH to the host.
66+- `-v anvil-data:/data` — a named volume holding the SQLite DB, the bare repos,
67+ and the persistent SSH **host key**. Use a named volume (not a host bind
68+ mount) so it's owned by the in-container `anvil` user.
69+
70+The baked config lives at `/etc/anvil/anvil.toml` (see `deploy/anvil.toml`).
71+Override it by bind-mounting your own file over that path.
72+
73+## 4. First run: create your account, key, and the repo
74+
75+```sh
76+# admin user
77+docker exec anvil anvild -c /etc/anvil/anvil.toml \
78+ user create collin --email you@example.com --password '<password>' --admin
79+
80+# your SSH public key (so you can push over SSH)
81+docker exec -i anvil anvild -c /etc/anvil/anvil.toml \
82+ user add-key collin --title laptop --key "$(cat ~/.ssh/id_ed25519.pub)"
83+
84+# the anvil repo itself
85+docker exec anvil anvild -c /etc/anvil/anvil.toml repo create collin/anvil \
86+ --description "a minimal git forge in Rust"
87+```
88+
89+(You can also create the user, add keys, and create repos from the web UI once
90+signed in — the CLI is just convenient for the first admin.)
91+
92+## 5. Self-host anvil on anvil
93+
94+From your local anvil checkout:
95+
96+```sh
97+git remote add origin ssh://git@anvil.richardscollin.com:2222/collin/anvil.git
98+git push -u origin main
99+```
100+
101+Then browse it at `https://anvil.richardscollin.com/collin/anvil`. HTTPS clone
102+also works: `git clone https://anvil.richardscollin.com/collin/anvil.git`
103+(pushes over HTTPS require your account password as the git password).
104+
105+## Operations
106+
107+- **Update**: re-run `./deploy/run.sh` (rebuilds the image, recreates the
108+ container; the `anvil-data` volume persists). NOTE: adding new DB tables in a
109+ future version won't auto-apply to an existing database yet (Toasty migration
110+ support is pending) — the git repos on disk are unaffected, but repo metadata
111+ in SQLite may need recreating until migrations land.
112+- **Backup**: snapshot the `anvil-data` volume, e.g.
113+ `docker run --rm -v anvil-data:/data -v "$PWD":/out debian:bookworm-slim \
114+ tar czf /out/anvil-data.tgz -C /data .`
115+- **Logs**: `docker logs -f anvil`.
116+- **System routes** live under `/-/` (e.g. sign in at
117+ `https://anvil.richardscollin.com/-/login`); `/{username}` is the user/repo
118+ namespace.
addedDockerfile+39 −0
1+# Multi-stage build for anvil.
2+#
3+# anvil is self-contained at runtime: SQLite (rusqlite) and the SSH crypto
4+# (aws-lc-rs, via russh) compile into the binary, and gix is pure-Rust — so the
5+# runtime image needs no git, no OpenSSH, and no system sqlite.
6+
7+# ---- build stage ----
8+FROM rust:1.95-bookworm AS build
9+
10+# aws-lc-sys (russh crypto) needs cmake; rusqlite "bundled" needs a C compiler
11+# (present in the full rust image). perl is used by the aws-lc build scripts.
12+RUN apt-get update \
13+ && apt-get install -y --no-install-recommends cmake clang perl \
14+ && rm -rf /var/lib/apt/lists/*
15+
16+WORKDIR /src
17+COPY . .
18+RUN cargo build --release --bin anvild
19+
20+# ---- runtime stage ----
21+FROM debian:bookworm-slim
22+
23+RUN apt-get update \
24+ && apt-get install -y --no-install-recommends ca-certificates \
25+ && rm -rf /var/lib/apt/lists/* \
26+ && useradd --system --user-group --home-dir /data anvil \
27+ && mkdir -p /data /etc/anvil \
28+ && chown -R anvil:anvil /data
29+
30+COPY --from=build /src/target/release/anvild /usr/local/bin/anvild
31+COPY deploy/anvil.toml /etc/anvil/anvil.toml
32+
33+# Web (proxied by Caddy over the internal network) and SSH (published to host).
34+EXPOSE 3000 2222
35+VOLUME /data
36+USER anvil
37+
38+ENTRYPOINT ["/usr/local/bin/anvild"]
39+CMD ["-c", "/etc/anvil/anvil.toml", "serve"]
addeddeploy/run.sh+28 −0
1+#!/usr/bin/env bash
2+# Build and (re)start the anvil container on hagrid.
3+#
4+# Run from the repo root, on the hagrid host (or anywhere with the `hagrid`
5+# Docker network). See DEPLOY.md for the full procedure (Caddy + DNS + admin).
6+set -euo pipefail
7+
8+IMAGE="${ANVIL_IMAGE:-anvil:latest}"
9+NETWORK="${ANVIL_NETWORK:-hagrid}"
10+SSH_PORT="${ANVIL_SSH_PORT:-2222}"
11+
12+cd "$(dirname "$0")/.."
13+
14+echo "==> building $IMAGE"
15+docker build -t "$IMAGE" .
16+
17+echo "==> (re)starting anvil container"
18+docker rm -f anvil 2>/dev/null || true
19+docker run -d \
20+ --name anvil \
21+ --network "$NETWORK" \
22+ --restart unless-stopped \
23+ -p "${SSH_PORT}:2222" \
24+ -v anvil-data:/data \
25+ "$IMAGE"
26+
27+echo "==> anvil is running (web: anvil:3000 via Caddy, ssh: host :${SSH_PORT})"
28+echo " Next: add the Caddy block + DNS, then create your admin user — see DEPLOY.md."