anvilsign in

collin/anvil · 7f97cf80

Cross-compile deploy via zigbuild (thin copy-binary image)

Collin Richards · 2026-06-09 20:36 UTC · 7f97cf80a075478e4316766b86bd524baafcb61a · parent b26efa9c · browse files

modified.gitignore+1 −0
⋯ 3 unchanged lines
44 *.db-wal
55 *.db-shm
66 anvil.toml
7+/deploy/anvild
modifiedDEPLOY.md+42 −5
⋯ 43 unchanged lines
4444 host). Caddy resolves `anvil:3000` by container name over the `hagrid` network,
4545 so the anvil container must join that network (the run script does this).
4646
47-## 3. Build & run the container
47+## 3. Build the image on your Mac, ship it to hagrid
48+
49+**Do not build on the VPS** — a release build needs ~2–4 GB peak and OOMs a
50+cheap, swap-less droplet. Instead, cross-compile a static binary on your Mac
51+(native speed, no QEMU) and copy it into a thin image.
52+
53+One-time toolchain setup:
54+
55+```sh
56+brew install zig
57+cargo install cargo-zigbuild
58+rustup target add x86_64-unknown-linux-musl
59+```
4860
49-On the hagrid host, from a checkout of this repo:
61+Then, from a checkout of this repo on your Mac:
5062
5163 ```sh
64+./deploy/build.sh
65+```
66+
67+That:
68+1. `cargo zigbuild --release --target x86_64-unknown-linux-musl` — cross-compiles
69+ a fully static `x86_64`-musl binary natively (~2 min, no emulation),
70+2. stages it at `deploy/anvild` and builds a thin image that just `COPY`s it in
71+ (the `Dockerfile` does no compilation — fast),
72+3. ships it: `docker save | gzip | ssh hagrid 'docker load'`.
73+
74+The VPS never compiles anything.
75+
76+## 4. Run the container on hagrid
77+
78+On the hagrid host (only runs docker — no build):
79+
80+```sh
5281 ./deploy/run.sh
5382 ```
5483
55-That builds `anvil:latest` and runs:
84+which does:
5685
5786 ```sh
5887 docker run -d --name anvil --network hagrid --restart unless-stopped \
⋯ 11 unchanged lines
7099 The baked config lives at `/etc/anvil/anvil.toml` (see `deploy/anvil.toml`).
71100 Override it by bind-mounting your own file over that path.
72101
73-## 4. First run: create your account, key, and the repo
102+> **If you must build on the VPS anyway** (not recommended): give it swap and
103+> cap parallelism, or it will OOM —
104+> ```sh
105+> sudo fallocate -l 4G /swapfile && sudo chmod 600 /swapfile \
106+> && sudo mkswap /swapfile && sudo swapon /swapfile # persist in /etc/fstab
107+> # then build with CARGO_BUILD_JOBS=1 (slow, but survives 1 GB RAM)
108+> ```
109+
110+## 5. First run: create your account, key, and the repo
74111
75112 ```sh
76113 # admin user
⋯ 12 unchanged lines
89126 (You can also create the user, add keys, and create repos from the web UI once
90127 signed in — the CLI is just convenient for the first admin.)
91128
92-## 5. Self-host anvil on anvil
129+## 6. Self-host anvil on anvil
93130
94131 From your local anvil checkout:
95132
⋯ 23 unchanged lines
modifiedDockerfile+8 −20
1-# Multi-stage build for anvil.
1+# anvil runtime image — just the prebuilt binary, no compilation in Docker.
22 #
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
3+# The binary is cross-compiled on the build host into a fully static
4+# x86_64-musl executable (see deploy/build.sh: `cargo zigbuild --target
5+# x86_64-unknown-linux-musl`), then staged at deploy/anvild and copied in here.
6+# So building this image is a fast `COPY` — no QEMU-emulated release build, and
7+# the VPS never compiles anything.
198
20-# ---- runtime stage ----
219 FROM debian:bookworm-slim
2210
2311 RUN apt-get update \
⋯ 3 unchanged lines
2715 && mkdir -p /data /etc/anvil \
2816 && chown -R anvil:anvil /data
2917
30-COPY --from=build /src/target/release/anvild /usr/local/bin/anvild
18+# Prebuilt static binary staged by deploy/build.sh.
19+COPY deploy/anvild /usr/local/bin/anvild
3120 COPY deploy/anvil.toml /etc/anvil/anvil.toml
3221
33-# Web (proxied by Caddy over the internal network) and SSH (published to host).
3422 EXPOSE 3000 2222
3523 VOLUME /data
3624 USER anvil
⋯ 3 unchanged lines
addeddeploy/build.sh+34 −0
1+#!/usr/bin/env bash
2+# Cross-compile anvil natively on the build host (Mac) and ship the image to
3+# hagrid. NOT run on the VPS — it never compiles anything.
4+#
5+# Uses cargo-zigbuild to cross-compile a fully static x86_64-musl binary at
6+# native speed (no QEMU), stages it at deploy/anvild, builds a thin image that
7+# just COPYs it in, and pipes the image to hagrid via docker load.
8+#
9+# Prereqs (one-time):
10+# brew install zig
11+# cargo install cargo-zigbuild
12+# rustup target add x86_64-unknown-linux-musl
13+set -euo pipefail
14+
15+IMAGE="${ANVIL_IMAGE:-anvil:latest}"
16+REMOTE="${ANVIL_REMOTE:-hagrid}"
17+TARGET="x86_64-unknown-linux-musl"
18+
19+cd "$(dirname "$0")/.."
20+
21+echo "==> cross-compiling anvild for $TARGET (native, via zig)"
22+cargo zigbuild --release --target "$TARGET" --bin anvild
23+
24+echo "==> staging binary at deploy/anvild"
25+cp "target/$TARGET/release/anvild" deploy/anvild
26+
27+echo "==> building $IMAGE (just COPYs the binary — fast)"
28+docker build --platform linux/amd64 -t "$IMAGE" .
29+
30+echo "==> shipping $IMAGE to $REMOTE"
31+docker save "$IMAGE" | gzip | ssh "$REMOTE" 'docker load'
32+
33+rm -f deploy/anvild
34+echo "==> done. On $REMOTE, run ./deploy/run.sh to (re)start the container."
modifieddeploy/run.sh+5 −11
11 #!/usr/bin/env bash
2-# Build and (re)start the anvil container on hagrid.
2+# (Re)start the anvil container on hagrid from an ALREADY-LOADED image.
33 #
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).
4+# Build and ship the image first with deploy/build.sh on a capable machine
5+# (the VPS can't compile it). This script only runs docker — no build — so it's
6+# safe on the low-RAM box. Standalone: needs only docker + the loaded image.
67 set -euo pipefail
78
89 IMAGE="${ANVIL_IMAGE:-anvil:latest}"
910 NETWORK="${ANVIL_NETWORK:-hagrid}"
1011 SSH_PORT="${ANVIL_SSH_PORT:-2222}"
1112
12-cd "$(dirname "$0")/.."
13-
14-echo "==> building $IMAGE"
15-docker build -t "$IMAGE" .
16-
17-echo "==> (re)starting anvil container"
1813 docker rm -f anvil 2>/dev/null || true
1914 docker run -d \
2015 --name anvil \
⋯ 3 unchanged lines
2419 -v anvil-data:/data \
2520 "$IMAGE"
2621
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."
22+echo "==> anvil (re)started from $IMAGE (web: anvil:3000 via Caddy, ssh: host :${SSH_PORT})"