Deploy: one static binary
A bri app AOT-compiles to a single static CGO_ENABLED=0 binary, byte-identical to the interpreter path (ADR 0071). The dev loop is a REPL (cljgo dev, live re-def, nREPL); the deploy artifact is one file with no runtime to distribute — the design bet the benchmarks measure (~20 MB image, ~30 ms cold-start, ~35 MB RSS under load — and ~2x fewer req/s than JVM http-kit, which the benchmarks page reports too).
Build a binary
Section titled “Build a binary”cljgo build -o server src/app/main.cljg # AOT-compile to one static binaryCGO_ENABLED=0 GOOS=linux cljgo build -o server src/app/main.cljg # for a Linux imagecljgo dist --target linux/amd64,linux/arm64 # or cross-compile a whole matrix at once (ADR 0077)The binary links the compiled core (never the interpreter), so it starts in single-digit milliseconds. It embeds nothing it doesn’t need — an app that never requires bri.core.telemetry never links the OpenTelemetry SDK (ADR 0074), and a db-less app never links SQLite/pgx (ADR 0076). For shipping binaries to many platforms at once (releases, Homebrew), see cljgo dist.
The Dockerfile
Section titled “The Dockerfile”cljgo new --template web ships a multi-stage Dockerfile that AOT-compiles the app to a static binary and copies it into a scratch image — typically single-digit MB plus your config and static assets:
# syntax=docker/dockerfile:1ARG CLJGO_VERSION=v0.8.9
FROM golang:1.26 AS buildARG CLJGO_VERSIONRUN go install github.com/muthuishere/cljgo/cmd/cljgo@${CLJGO_VERSION}WORKDIR /appCOPY . .RUN CGO_ENABLED=0 GOOS=linux cljgo build -o /server src/app/main.cljg
FROM scratchCOPY --from=build /server /serverCOPY --from=build /app/conf.edn /conf.ednCOPY --from=build /app/public /publicENV APP_PROFILE=prodEXPOSE 3000ENTRYPOINT ["/server"]The build stage go installs the cljgo compiler at a pinned CLJGO_VERSION and fetches the matching runtime from the Go module proxy — no repo checkout. Bump CLJGO_VERSION to move to a newer cljgo.
docker build -t app .docker run -p 3000:3000 appConfig and secrets in the image
Section titled “Config and secrets in the image”conf.edn defaults :port to 3000; any key is overridden by an APP_* env var (the secrets-are-env doctrine), so the same image runs in every environment — see bri.core.config:
docker run -p 3000:3000 \ -e APP_PROFILE=prod \ -e APP_DB_URL=postgres://… \ -e APP_AUTH__SECRET=… \ appSecrets never live in the image — they arrive as env at run time. APP_DB_URL flips cljg.data.cast from the zero-install SQLite default to Postgres with no code change; APP_AUTH__SECRET is the bri.core.security signing key that prod must set.
Migrations on deploy
Section titled “Migrations on deploy”(db/migrate! conn "migrations") is idempotent and forward-only — run it on boot (the delay pattern) or as a one-shot before rollout. It is the same call in dev and prod; running it twice is a no-op. See cljg.data.cast.
Ops endpoints
Section titled “Ops endpoints”The API stack serves ops endpoints by default: GET /healthz (liveness), GET /readyz (runs :ready-checks → 200/503), and GET /metrics (Prometheus text) — wire them straight into your orchestrator’s probes. Guard /metrics in prod with :metrics-guard (auth/admin-only).
Where next
Section titled “Where next”- Compile & ship binaries — the general
cljgo buildstory - bri.core.config — profiles,
APP_*env, and the schema - cljg.data.cast — SQLite by default, Postgres via
APP_DB_URL - Benchmarks — the measured image / cold-start / RSS figures
Questions or feedback on this page? Comment below with your GitHub account — comments are public and live in the project's GitHub Discussions.