# Building from source

> Build GroundShade locally or for production. Local Docker images, multi-arch builds, Cargo profiles, cache strategies, and image layout.

Three audiences, three paths.

- **Just need a binary.** `make build-release` produces
  `target/release/groundshade`.
- **Self-hosting from Docker.** Use the published image
  (`codeberg.org/groundshade/groundshade:latest`) or build your own with
  `make docker`.
- **Cutting a release** (maintainer only). `make release TAG=vX.Y.Z`
  validates the working tree, runs CI, tags the commit, pushes the
  tag, and publishes the multi-arch image.

The builder image pins Rust 1.95 via the `RUST_VERSION` ARG in the
Dockerfile.

## Local Docker image

For local iteration:

```sh
make docker
docker run --rm -p 8080:8080 -p 127.0.0.1:9090:9090 \
  -e GROUNDSHADE_ADMIN_TOKEN=dev \
  groundshade/groundshade:dev
```

`make docker` uses the `release-fast` Cargo profile: release
semantics, no fat LTO, 16-way codegen. Compiles 2-3x faster than
full release with a small (under 5%) runtime cost. Fine for local;
tagged releases use full `release`.

## Multi-arch (arm64)

`make release-push` defaults to `linux/amd64`. Emulated arm64 via
QEMU on an x86 host takes 4-5x longer than native amd64. To opt in:

```sh
# Emulated. Slow; run once at release time, not in a tight loop.
make release-push TAG=v0.1.2 PLATFORMS=linux/amd64,linux/arm64

# Native via buildx node on an Apple Silicon mac or arm64 runner.
docker buildx create --name multi --driver docker-container --use
docker buildx inspect --bootstrap multi
make release-push TAG=v0.1.2 PLATFORMS=linux/amd64,linux/arm64
```

If you have a real arm64 machine on the network, attach it as a
buildx node so each platform builds natively:

```sh
docker buildx create --name multi --use            # local node (amd64)
docker buildx create --append --name multi \
  ssh://you@arm64-host                              # remote arm64 node
docker buildx build --platform linux/amd64,linux/arm64 ...
```

## Cache strategies

Cold cargo-chef build: 3-5 minutes for amd64 native, 10-20 minutes
for multi-arch via QEMU. After the first build, edits to workspace
crates rebuild in 30-60 seconds thanks to the dep layer cache.

Three ways to persist the cache:

- **Local layer cache** (default). Works on the same machine.
  Cache lives in Docker's layer store.
- **Registry-backed cache.** Pushes the dep-build layer to a
  registry, so a fresh machine (CI runner, colleague's laptop) can
  pull it.

  ```sh
  docker buildx build \
    --cache-to   type=registry,ref=codeberg.org/groundshade/groundshade-cache:buildcache,mode=max \
    --cache-from type=registry,ref=codeberg.org/groundshade/groundshade-cache:buildcache \
    --platform linux/amd64 \
    --tag codeberg.org/groundshade/groundshade:dev \
    --push .
  ```

- **sccache.** Shared rustc cache across crates and projects. Most
  useful when you build several Rust projects on the same machine.
  Set `RUSTC_WRAPPER=sccache` and start `sccache --start-server`
  before `cargo build`.

For a single-project setup, the layer cache is enough. Registry
cache pays off for ephemeral CI runners.

## Cargo profiles

| Profile | LTO | codegen-units | Use |
|---|---|---|---|
| `dev` | off | 256 | `cargo run`, fastest iteration |
| `release-fast` | off | 16 | Local Docker images, dev clusters |
| `release` | thin | 1 | Tagged production releases |

`release-fast` is the right default for anyone building their own
image. Use `release` when you cut a tag for real traffic.

## Image layout

Two stages:

- **builder.** `rust:1.95-bookworm` with `cargo-chef` installed.
  Plans the dependency graph from current sources, cooks deps in
  their own layer, then compiles the workspace.
- **runtime.** `debian:bookworm-slim` with `ca-certificates`,
  `wget` (for the healthcheck), and `tini` (so `docker kill -s
  HUP` reaches the binary cleanly). Runs as non-root UID 65532.
  Final image: around 140 MB.

State persists in `/var/lib/groundshade` (the HMAC signing key for
trust tokens, see [operating](/docs/operating)). The image
declares `/var/lib/groundshade` as a volume; mount it in
production.

Default container env:

- `GROUNDSHADE_LISTEN_HTTP=0.0.0.0:8080`
- `GROUNDSHADE_LISTEN_ADMIN=0.0.0.0:9090`
- `GROUNDSHADE_TRUST_STATE_DIR=/var/lib/groundshade`

The admin listener binds `0.0.0.0` inside the container; publish
the host port to loopback only (`"127.0.0.1:9090:9090"`).

## Why not distroless

The runtime image needs `wget` for the `HEALTHCHECK` and `tini` for
SIGHUP forwarding. A distroless base would drop both. Debian-slim
is 25 MB heavier and worth it for the operational ergonomics. If
you skip the Docker healthcheck and SIGHUP reload, building a
distroless variant is a 10-line change to the runtime stage.
