documentation
building from source
Build GroundShade locally or for production. Local Docker images, multi-arch builds, Cargo profiles, cache strategies, and image layout.
updated
Three audiences, three paths.
- Just need a binary.
make build-releaseproducestarget/release/groundshade. - Self-hosting from Docker. Use the published image
(
codeberg.org/groundshade/groundshade:latest) or build your own withmake docker. - Cutting a release (maintainer only).
make release TAG=vX.Y.Zvalidates 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:
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:
# 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:
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.
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=sccacheand startsccache --start-serverbeforecargo 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-bookwormwithcargo-chefinstalled. Plans the dependency graph from current sources, cooks deps in their own layer, then compiles the workspace. - runtime.
debian:bookworm-slimwithca-certificates,wget(for the healthcheck), andtini(sodocker kill -s HUPreaches 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). The image
declares /var/lib/groundshade as a volume; mount it in
production.
Default container env:
GROUNDSHADE_LISTEN_HTTP=0.0.0.0:8080GROUNDSHADE_LISTEN_ADMIN=0.0.0.0:9090GROUNDSHADE_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.