Distroless, non-root base images for Java, Python, Node, Go, and Rust — plus nginx and
Redis. musl by default for a smaller libc; glibc one tag away. No shell, no package
manager, no root — almost nothing for an attacker to land on, and far less to patch.
# build your static binary where the toolchain lives…FROMgolang:1-bookworm AS buildCOPY . .RUN CGO_ENABLED=0 go build -o /out/app .# …then ship just the binary: non-root, no shell, no OSFROMghcr.io/mathiasror/varde-go:latestCOPY --from=build /out/app /app/app
0 shells
non-root
musl by default
SBOM per image
free & open
The catalog
Runtimes, services, any binary.
Each image is just the runtime — or, for a compiled binary, just its libc: non-root, no shell, musl by default. A scannable SBOM per image, multi-arch, on GitHub Container Registry.
varde-jre
17*
21
25
JVM apps (Java / Kotlin). Prebuilt headless Temurin JRE — drop a fat JAR at /app/app.jar. *17 is glibc-only.
FROM ghcr.io/mathiasror/varde-jre:21
varde-python
3.11
3.12
3.13
Python apps. The CPython interpreter only — no pip, no shell. COPY your resolved deps in.
FROM ghcr.io/mathiasror/varde-python:3.13
varde-node
22
24
Node.js apps. The node runtime only — no npm. COPY node_modules plus your code.
FROM ghcr.io/mathiasror/varde-node:24
varde-php
8.3
8.4
8.5
PHP apps. The interpreter with the stock extension set plus php-fpm — no composer, no pecl. COPY vendor/ plus your code.
FROM ghcr.io/mathiasror/varde-php:8.5
varde-nginx
latest
Static sites, non-root on :8080. COPY site/ /app/ and serve — no root, no shell.
FROM ghcr.io/mathiasror/varde-nginx:latest
varde-redis
latest
The Redis server, distroless and non-root. Run as-is or drop in a redis.conf.
FROM ghcr.io/mathiasror/varde-redis:latest
varde-postgres
16
17
18
The PostgreSQL server, distroless and non-root. One explicit initdb against a volume at /app, then it runs as-is. Ships a minimal /bin/sh (ash) — initdb requires one; the sole exception to no-shell.
FROM ghcr.io/mathiasror/varde-postgres:18
varde-mysql
8.4
MySQL 8.4 LTS, distroless and non-root. Native no-shell initialization — one --initialize-insecure run, then serve. glibc-only.
FROM ghcr.io/mathiasror/varde-mysql:8.4
varde-rabbitmq
latest
RabbitMQ with a shell-free Erlang boot — the VM is exec'd directly; epmd and the escript CLI work without a single script.
FROM ghcr.io/mathiasror/varde-rabbitmq:latest
varde-memcached
latest
The Memcached server, distroless and non-root. Run as-is or pass flags via CMD — no config file needed.
FROM ghcr.io/mathiasror/varde-memcached:latest
varde-static · glibc · musl
latest
Any compiled binary at /app/app: static (Go CGO_ENABLED=0), glibc (a default cargo build), or musl. varde-go & varde-rust alias the first two.
FROM ghcr.io/mathiasror/varde-static:latest
Every image
Runs as 1000:1000, non-root
musl by default — -glibc tag to opt in
Multi-arch amd64 + arm64 (:21-musl-arm64)
CycloneDX SBOM with NVD CPEs — scan with grype
Rebuilt weekly so CVE fixes flow through
Why varde
Less in the image, less to defend.
A varde — the Norwegian cairn, a stack of stones that marks a trail — carries only the stones that mark the way. These images carry only what your app needs to run.
Distroless
No shell, no package manager, no busybox, no coreutils. Just the runtime — nothing for an attacker to pivot through.
Non-root by default
Every container runs as uid/gid 1000:1000 in a /app it owns. No root user is even present.
Multi-arch
Native linux/amd64 and linux/arm64, selectable by tag (e.g. :21-musl-arm64). Same image, your architecture.
Scannable by design
A distroless image has no OS package DB — so a Nix-closure CycloneDX SBOM covers system packages and the runtime. Scan it with grype.
Built with Nix
Reproducible builds from a committed lockfile. A weekly lock-bump commit flows CVE fixes through — every image traces to a commit.
Drop your app in
Bring one real artifact — a fat JAR, a binary, code plus its deps — and add two lines of Dockerfile. The entrypoint runs it directly.
Usage
Two lines on top of your build.
Build your artifact in a stage that has the tooling, then COPY it onto varde. There's no shell, so the entrypoint runs your app directly.
Dockerfile · Spring Boot / fat JAR
# build stage: Gradle bootJar -> one self-contained fat jarFROMgradle:8.14-jdk21 AS buildWORKDIR/home/gradle/srcCOPY --chown=gradle:gradle . .RUN gradle bootJar --no-daemonFROMghcr.io/mathiasror/varde-jre:21# base ENTRYPOINT is java -jar /app/app.jar — just place the jarCOPY --from=build /home/gradle/src/build/libs/app.jar /app/app.jar
Dockerfile · interpreter + deps
# deps stage: alpine = musl, matching the default musl runtimeFROMpython:3.13-alpine AS depsWORKDIR/buildCOPY requirements.txt .RUN pip install --no-cache-dir --target=/app/site-packages -r requirements.txtFROMghcr.io/mathiasror/varde-python:3.13COPY --from=deps /app/site-packages /app/site-packagesCOPY main.py /app/main.py# no shell to "activate a venv" — put the deps on the import pathENV PYTHONPATH=/app/site-packages
Dockerfile · runtime + node_modules
# deps stage: alpine = musl, so native addons match the runtimeFROMnode:24-alpine AS depsWORKDIR/buildCOPY package*.json ./RUN npm ci --omit=devFROMghcr.io/mathiasror/varde-node:24COPY --from=deps /build/node_modules /app/node_modulesCOPY src/ /app/src/# no npm start — point CMD straight at a real fileCMD ["/app/src/server.js"]
Dockerfile · static binary
# build stage: CGO_ENABLED=0 -> a fully static binaryFROMgolang:1-bookworm AS buildWORKDIR/srcCOPY . .RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/app .FROMghcr.io/mathiasror/varde-static:latest# base ENTRYPOINT is /app/app — just drop the binary inCOPY --from=build /out/app /app/app
Dockerfile · glibc binary
# build stage: a default cargo build links glibc -> varde-glibcFROMrust:1-bookworm AS buildWORKDIR/srcCOPY . .RUN cargo build --release --lockedFROMghcr.io/mathiasror/varde-glibc:latest# base ENTRYPOINT is /app/app — just drop the binary inCOPY --from=build /src/target/release/app /app/app
Dockerfile · static site on :8080
FROMghcr.io/mathiasror/varde-nginx:latestCOPY site/ /app/# serves /app on :8080, non-root
Nix builds each image from an exact dependency closure. That same closure becomes the SBOM — so what grype scans is precisely what's in the image, down to glibc and zlib.
Reproducible closure
Nix pins the exact runtime and system libraries — and only those.
Exact CycloneDX SBOM
sbomnix emits a CycloneDX SBOM with CPEs from that closure.
grype scans it all
System packages and runtime are scannable — not just your app deps.
Free & open
No paywall. No token. No “contact sales.”
A hardened base image shouldn't come with a sales call, a personal access token, and a per-seat licence. varde is public and yours: read the entire Nix build, scan the SBOM, and docker pull without an account.
varde
Free & open source
Free forever — every image, every tag, every arch
Anonymous docker pull — no token, no login
Run it across your org — no per-seat or per-image licence
The whole build is public — audit the flake yourself
SBOMs included for everyone, never an upsell
Other “hardened” images
Free*terms apply
The catalog that matters sits behind paid tiers or “contact sales”
Pulls gated by a personal access token or a paid registry