collin/anvil
b26efa9c0a4475d903149a1c8169e36b4dd081ec / DEPLOY.md
RenderedSource
| 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. |