| 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. |