diff --git a/README.md b/README.md index f35d2b8..b71aa8a 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ [![Hadolint](https://img.shields.io/badge/Hadolint-clean-success)](https://github.com/hadolint/hadolint) [![Prettier](https://img.shields.io/badge/Code_Style-Prettier-F7B93E?logo=prettier&logoColor=black)](https://prettier.io/) -Reference [Dockerfiles](https://docs.docker.com/engine/reference/builder/) and best-practice guides for building **secure container images** that ship to production: small, signed, scanner-friendly, [OCI](https://opencontainers.org/)-compliant. [Python](https://www.python.org/), [Go](https://go.dev/), [JAX](https://jax.readthedocs.io/), [Node.js](https://nodejs.org/), [TypeScript](https://www.typescriptlang.org/), [Rust](https://www.rust-lang.org/), and [Java](https://openjdk.org/) are covered. +Reference [Dockerfiles](https://docs.docker.com/engine/reference/builder/) and best-practice guides for building **secure container images** that ship to production: small, signed, scanner-friendly, [OCI](https://opencontainers.org/)-compliant. [Python](https://www.python.org/), [Go](https://go.dev/), [JAX](https://jax.readthedocs.io/), [Node.js](https://nodejs.org/), [TypeScript](https://www.typescriptlang.org/), [Rust](https://www.rust-lang.org/), [Java](https://openjdk.org/), and [Zig](https://ziglang.org/) are covered. ## Why these templates @@ -147,11 +147,21 @@ Multi-stage [Java](https://openjdk.org/) images supporting both Maven and Gradle Full documentation: [`dockerfiles/java/README.md`](dockerfiles/java/README.md). +### Zig — tarball builder + Distroless static + +Multi-stage [Zig](https://ziglang.org/) images that download and SHA-256-verify the official Zig toolchain tarball before use. Produces fully-static binaries (musl libc bundled in the toolchain) that ship in [**Google Distroless static**](https://github.com/GoogleContainerTools/distroless) — the smallest possible runtime: no libc, no shell, no package manager. + +- Distroless static image → [`dockerfiles/zig/Dockerfile.zig`](dockerfiles/zig/Dockerfile.zig) +- Chainguard static image → [`dockerfiles/zig/Dockerfile.zig.chainguard`](dockerfiles/zig/Dockerfile.zig.chainguard) +- VS Code devcontainer (Zig + ZLS + gdb + lldb) → [`dockerfiles/zig/Dockerfile.devcontainer`](dockerfiles/zig/Dockerfile.devcontainer) + +Full documentation: [`dockerfiles/zig/README.md`](dockerfiles/zig/README.md). + ### Coming soon Tracked as issues — comment or 👍 to bump priority. -- [Zig Dockerfile templates](https://github.com/kssd/dockerfiles/issues/11) _(planned)_ +_All planned ecosystems are now available. Open an issue to request a new one._ ## Guides diff --git a/dockerfiles/zig/.devcontainer/devcontainer.json b/dockerfiles/zig/.devcontainer/devcontainer.json new file mode 100644 index 0000000..e16818c --- /dev/null +++ b/dockerfiles/zig/.devcontainer/devcontainer.json @@ -0,0 +1,29 @@ +{ + "name": "Zig", + "build": { + "dockerfile": "../Dockerfile.devcontainer", + "context": "..", + "args": { + "VARIANT": "bookworm", + "ZIG_VERSION": "0.16.0", + "ZLS_VERSION": "0.16.0" + } + }, + "remoteUser": "vscode", + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", + "workspaceFolder": "/workspace", + "customizations": { + "vscode": { + "extensions": ["ziglang.vscode-zig", "vadimcn.vscode-lldb", "usernamehw.errorlens"], + "settings": { + "zig.zls.path": "/usr/local/bin/zls", + "zig.path": "/usr/local/bin/zig", + "editor.formatOnSave": true, + "[zig]": { + "editor.defaultFormatter": "ziglang.vscode-zig" + } + } + } + }, + "postCreateCommand": "zig version && zls --version" +} diff --git a/dockerfiles/zig/.dockerignore b/dockerfiles/zig/.dockerignore new file mode 100644 index 0000000..f0c3daf --- /dev/null +++ b/dockerfiles/zig/.dockerignore @@ -0,0 +1,30 @@ +# Zig build cache and output -- can be large; always exclude +zig-cache/ +.zig-cache/ +zig-out/ + +# Editor and IDE +.vscode/ +.idea/ +*.swp +*.swo + +# Devcontainer +.devcontainer/ + +# Credentials and secrets +.env +*.pem +*.key +*.cert + +# CI +.github/ + +# Git +.git/ +.gitignore + +# Documentation +*.md +LICENSE diff --git a/dockerfiles/zig/Dockerfile.devcontainer b/dockerfiles/zig/Dockerfile.devcontainer new file mode 100644 index 0000000..3b66303 --- /dev/null +++ b/dockerfiles/zig/Dockerfile.devcontainer @@ -0,0 +1,68 @@ +# Zig devcontainer image (Debian bookworm-slim + Zig + ZLS). +# +# Installs Zig and ZLS (Zig Language Server) from official tarballs, both +# pinned by version and SHA-256. Versions must match: ZLS_VERSION = ZIG_VERSION. +# +# ZIG_VERSION / ZLS_VERSION: +# Update both together. Fetch new SHA-256 values from: +# https://ziglang.org/download/ (Zig) +# https://github.com/zigtools/zls/releases (ZLS) +# +# Non-root vscode user (UID 1000) from the base image. +# +# Build context: dockerfiles/zig/ +# docker build -f Dockerfile.devcontainer . +ARG VARIANT=bookworm +ARG ZIG_VERSION=0.16.0 +ARG ZLS_VERSION=0.16.0 + +FROM mcr.microsoft.com/devcontainers/base:1-${VARIANT} + +ARG ZIG_VERSION +ARG ZLS_VERSION +ARG TARGETARCH + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +RUN apt-get update && apt-get install -y --no-install-recommends \ + xz-utils=5.4.1-0.2 \ + gdb=13.1-3 \ + lldb=1:14.0-55.7~deb12u1 \ + valgrind=3.19.0-1 \ + && rm -rf /var/lib/apt/lists/* + +# Install Zig +RUN case "${TARGETARCH}" in \ + amd64) \ + ZIG_URL="https://ziglang.org/download/${ZIG_VERSION}/zig-x86_64-linux-${ZIG_VERSION}.tar.xz" ; \ + ZIG_SHA256="70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00" ;; \ + arm64) \ + ZIG_URL="https://ziglang.org/download/${ZIG_VERSION}/zig-aarch64-linux-${ZIG_VERSION}.tar.xz" ; \ + ZIG_SHA256="ea4b09bfb22ec6f6c6ceac57ab63efb6b46e17ab08d21f69f3a48b38e1534f17" ;; \ + *) echo "Unsupported TARGETARCH: ${TARGETARCH}" && exit 1 ;; \ + esac && \ + curl -fsSL "${ZIG_URL}" -o /tmp/zig.tar.xz && \ + echo "${ZIG_SHA256} /tmp/zig.tar.xz" | sha256sum -c - && \ + tar -xJf /tmp/zig.tar.xz -C /usr/local && \ + ln -s "/usr/local/zig-"*"-linux-"*"-${ZIG_VERSION}/zig" /usr/local/bin/zig && \ + rm /tmp/zig.tar.xz + +# Install ZLS (Zig Language Server) +# Update ZLS_SHA256 values together with ZLS_VERSION. +# Fetch from: https://github.com/zigtools/zls/releases +RUN case "${TARGETARCH}" in \ + amd64) \ + ZLS_URL="https://github.com/zigtools/zls/releases/download/${ZLS_VERSION}/zls-x86_64-linux.tar.xz" ; \ + ZLS_SHA256="ded6d562a0b86ee878b1ddf70ffab2797ce3cdca3b02d6077548f9d56dff96b6" ;; \ + arm64) \ + ZLS_URL="https://github.com/zigtools/zls/releases/download/${ZLS_VERSION}/zls-aarch64-linux.tar.xz" ; \ + ZLS_SHA256="430cd293d201eb70ae2519dbc96c854bf8791b8df7fc9392e8d2dc9680a2bed7" ;; \ + *) echo "Unsupported TARGETARCH: ${TARGETARCH}" && exit 1 ;; \ + esac && \ + curl -fsSL "${ZLS_URL}" -o /tmp/zls.tar.xz && \ + echo "${ZLS_SHA256} /tmp/zls.tar.xz" | sha256sum -c - && \ + tar -xJf /tmp/zls.tar.xz -C /tmp && \ + mv /tmp/zls /usr/local/bin/zls && \ + chmod +x /usr/local/bin/zls && \ + rm /tmp/zls.tar.xz + +USER vscode diff --git a/dockerfiles/zig/Dockerfile.zig b/dockerfiles/zig/Dockerfile.zig new file mode 100644 index 0000000..59508fc --- /dev/null +++ b/dockerfiles/zig/Dockerfile.zig @@ -0,0 +1,81 @@ +# Zig application image (tarball builder + Google Distroless static, multi-stage). +# +# Downloads and SHA-256-verifies the official Zig toolchain tarball before +# use — the tarball is the supply-chain entry point for Zig builds, so hash +# pinning is mandatory (Zig does not ship signed packages via a package manager). +# +# Multi-arch note: +# The builder selects the correct tarball URL and SHA-256 based on +# TARGETARCH (amd64 -> x86_64, arm64 -> aarch64). The runtime stage is +# architecture-neutral (distroless/static ships a per-arch image). +# +# Optimise mode (ZIG_OPTIMIZE): +# ReleaseSafe (default) — optimised + safety checks (bounds, overflow). +# ReleaseFast — maximum speed, safety checks disabled. +# ReleaseSmall — minimum binary size, safety checks disabled. +# +# BIN_NAME must match the executable name in build.zig (root.name field). +# +# Runtime: gcr.io/distroless/static-debian12:nonroot — no libc, no shell. +# Zig targets musl libc by default (bundled), producing fully-static binaries +# that run without any shared libraries. +# +# Build: +# docker build --build-arg BIN_NAME=myapp -t myapp -f Dockerfile.zig . +# +# Multi-arch: +# docker buildx build --platform=linux/amd64,linux/arm64 \ +# --build-arg BIN_NAME=myapp -t myapp -f Dockerfile.zig . +# +# Run (hardened): +# docker run --rm \ +# --read-only \ +# --cap-drop=ALL \ +# --security-opt=no-new-privileges \ +# myapp +ARG ZIG_VERSION=0.16.0 +ARG DISTROLESS_TAG=debian12 + +FROM --platform=$BUILDPLATFORM debian:bookworm-slim AS builder +ARG ZIG_VERSION +ARG TARGETARCH +ARG BIN_NAME=app +ARG ZIG_OPTIMIZE=ReleaseSafe + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates=20230311 \ + curl=7.88.1-10+deb12u12 \ + xz-utils=5.4.1-0.2 \ + && rm -rf /var/lib/apt/lists/* + +# Select tarball URL and SHA-256 by target architecture. +# Update both values together when upgrading ZIG_VERSION. +RUN case "${TARGETARCH}" in \ + amd64) \ + ZIG_URL="https://ziglang.org/download/${ZIG_VERSION}/zig-x86_64-linux-${ZIG_VERSION}.tar.xz" ; \ + ZIG_SHA256="70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00" ;; \ + arm64) \ + ZIG_URL="https://ziglang.org/download/${ZIG_VERSION}/zig-aarch64-linux-${ZIG_VERSION}.tar.xz" ; \ + ZIG_SHA256="ea4b09bfb22ec6f6c6ceac57ab63efb6b46e17ab08d21f69f3a48b38e1534f17" ;; \ + *) echo "Unsupported TARGETARCH: ${TARGETARCH}" && exit 1 ;; \ + esac && \ + curl -fsSL "${ZIG_URL}" -o /tmp/zig.tar.xz && \ + echo "${ZIG_SHA256} /tmp/zig.tar.xz" | sha256sum -c - && \ + tar -xJf /tmp/zig.tar.xz -C /usr/local && \ + ln -s "/usr/local/zig-"*"-linux-"*"-${ZIG_VERSION}/zig" /usr/local/bin/zig && \ + rm /tmp/zig.tar.xz + +WORKDIR /src +COPY build.zig build.zig.zon* ./ +RUN zig build --fetch || true +COPY . . +RUN zig build -Doptimize="${ZIG_OPTIMIZE}" && \ + cp "zig-out/bin/${BIN_NAME}" /server + +FROM gcr.io/distroless/static-${DISTROLESS_TAG}:nonroot +COPY --from=builder --chown=nonroot:nonroot /server /app/server + +USER nonroot + +ENTRYPOINT ["/app/server"] diff --git a/dockerfiles/zig/Dockerfile.zig.chainguard b/dockerfiles/zig/Dockerfile.zig.chainguard new file mode 100644 index 0000000..cd696f3 --- /dev/null +++ b/dockerfiles/zig/Dockerfile.zig.chainguard @@ -0,0 +1,63 @@ +# Zig application image (tarball builder on Wolfi + Chainguard static, multi-stage). +# +# Chainguard sibling of Dockerfile.zig. Uses cgr.dev/chainguard/wolfi-base +# as the builder (cgr.dev/chainguard/zig:latest is a paid tier) and ships +# in cgr.dev/chainguard/static — daily-rebuilt, Sigstore-signed, with SLSA +# provenance and SBOMs attached. +# +# Free vs. paid Chainguard tags: +# The free Developer Edition only publishes :latest. Versioned tags +# require a paid Chainguard subscription. For free-tier reproducibility, +# pin by digest instead of relying on :latest: +# +# docker pull cgr.dev/chainguard/static:latest +# docker inspect --format='{{index .RepoDigests 0}}' cgr.dev/chainguard/static:latest +# # bake the digest into the FROM line: +# FROM cgr.dev/chainguard/static@sha256: +# +# Refresh the digest deliberately (e.g. weekly) and re-scan. +# +# Build: +# docker build --build-arg BIN_NAME=myapp -t myapp -f Dockerfile.zig.chainguard . +ARG ZIG_VERSION=0.16.0 +ARG BASE_TAG=latest + +FROM --platform=$BUILDPLATFORM cgr.dev/chainguard/wolfi-base:${BASE_TAG} AS builder +ARG ZIG_VERSION +ARG TARGETARCH +ARG BIN_NAME=app +ARG ZIG_OPTIMIZE=ReleaseSafe + +RUN apk add --no-cache curl=7.87.0-r0 xz=5.6.4-r2 + +SHELL ["/bin/ash", "-eo", "pipefail", "-c"] + +# Select tarball URL and SHA-256 by target architecture. +RUN case "${TARGETARCH}" in \ + amd64) \ + ZIG_URL="https://ziglang.org/download/${ZIG_VERSION}/zig-x86_64-linux-${ZIG_VERSION}.tar.xz" ; \ + ZIG_SHA256="70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00" ;; \ + arm64) \ + ZIG_URL="https://ziglang.org/download/${ZIG_VERSION}/zig-aarch64-linux-${ZIG_VERSION}.tar.xz" ; \ + ZIG_SHA256="ea4b09bfb22ec6f6c6ceac57ab63efb6b46e17ab08d21f69f3a48b38e1534f17" ;; \ + *) echo "Unsupported TARGETARCH: ${TARGETARCH}" && exit 1 ;; \ + esac && \ + curl -fsSL "${ZIG_URL}" -o /tmp/zig.tar.xz && \ + echo "${ZIG_SHA256} /tmp/zig.tar.xz" | sha256sum -c - && \ + tar -xJf /tmp/zig.tar.xz -C /usr/local && \ + ln -s "/usr/local/zig-"*"-linux-"*"-${ZIG_VERSION}/zig" /usr/local/bin/zig && \ + rm /tmp/zig.tar.xz + +WORKDIR /src +COPY build.zig build.zig.zon* ./ +RUN zig build --fetch || true +COPY . . +RUN zig build -Doptimize="${ZIG_OPTIMIZE}" && \ + cp "zig-out/bin/${BIN_NAME}" /server + +FROM cgr.dev/chainguard/static:${BASE_TAG} +COPY --from=builder /server /app/server + +USER nonroot + +ENTRYPOINT ["/app/server"] diff --git a/dockerfiles/zig/README.md b/dockerfiles/zig/README.md new file mode 100644 index 0000000..7fd4f56 --- /dev/null +++ b/dockerfiles/zig/README.md @@ -0,0 +1,207 @@ +# Secure Zig Docker Image Templates + +Reference Dockerfiles for building **secure Zig Docker images** in production — a **Distroless static** variant, a **Chainguard** variant for signed/attested images, and a **devcontainer** for VS Code. + +**Default runtime is [`gcr.io/distroless/static-debian12:nonroot`](https://github.com/GoogleContainerTools/distroless).** Zig targets musl libc by default (bundled in the toolchain), producing fully-static binaries that need no shared libraries at runtime. + +| File | Builder | Runtime base | Use when | +| --------------------------- | ----------------------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------- | +| `Dockerfile.zig` | `debian:bookworm-slim` + Zig tarball | `gcr.io/distroless/static-debian12:nonroot` | Default. Fully-static binary, no libc dependency, smallest final image. | +| `Dockerfile.zig.chainguard` | `cgr.dev/chainguard/wolfi-base` + tarball | `cgr.dev/chainguard/static` | Chainguard daily CVE patches, Sigstore signatures, SLSA provenance. | +| `Dockerfile.devcontainer` | — | `mcr.microsoft.com/devcontainers/base:1-bookworm` | VS Code Remote-Containers / Dev Containers development environment. | + +## Why the tarball approach — and why SHA-256 verification matters + +Zig does not ship packages through `apt`, `apk`, or any other OS package manager. The official distribution channel is a direct tarball from `ziglang.org`. Because this is an unsigned download (no GPG, no apt signature), the only integrity check is the SHA-256 hash published on the Zig downloads page. + +The Dockerfiles pin **both** the version and the SHA-256 hash and verify them with `sha256sum -c` before extracting. A tampered or substituted tarball fails the build. + +```dockerfile +RUN curl -fsSL "${ZIG_URL}" -o /tmp/zig.tar.xz && \ + echo "${ZIG_SHA256} /tmp/zig.tar.xz" | sha256sum -c - && \ + tar -xJf /tmp/zig.tar.xz ... +``` + +When upgrading `ZIG_VERSION`, update both the version ARG and the SHA-256 constants. Fetch fresh hashes from [ziglang.org/download](https://ziglang.org/download/). + +## Why distroless/static works for Zig + +Zig uses a bundled musl libc by default. Unless you explicitly link against a system library with `-lc` (which pulls in glibc), the output binary has no shared library dependencies — it is fully self-contained. `ldd` on a default Zig release binary returns `not a dynamic executable`. + +`gcr.io/distroless/static` contains only: + +- CA certificates (for TLS) +- `/etc/passwd` and `/etc/group` with the `nonroot` user +- tzdata + +There is no shell, no package manager, no curl, no libc. A compromised process cannot drop to a shell or install tools from the image. + +If your application links a C library via `-lc` (glibc), switch the runtime to `gcr.io/distroless/cc-debian12:nonroot`. + +## Optimize modes (`ZIG_OPTIMIZE`) + +| Mode | Safety checks | Speed | Binary size | Use when | +| -------------- | ------------- | -------- | ----------- | ---------------------------------------- | +| `ReleaseSafe` | Yes | Fast | Medium | Default. Production with safe defaults. | +| `ReleaseFast` | No | Fastest | Medium | Maximum throughput; no bounds checks. | +| `ReleaseSmall` | No | Moderate | Smallest | Embedded targets, Lambda, tight budgets. | +| `Debug` | Yes | Slow | Large | Development; never ship to production. | + +Pass `--build-arg ZIG_OPTIMIZE=ReleaseFast` to override. + +## `zig build` vs `zig build-exe` + +The Dockerfiles use `zig build`, which reads `build.zig` and outputs to `zig-out/bin/`. This is the recommended approach for applications because it respects project-level build options, dependencies, and custom steps. + +For a quick one-off binary without `build.zig`: + +```dockerfile +RUN zig build-exe -O ReleaseSafe -target x86_64-linux-musl src/main.zig -femit-bin=/server +``` + +`zig build-exe` outputs directly to the specified path — no `zig-out/` directory. Use this only for single-file programs; real projects should use `build.zig`. + +## Cross-compilation + +Zig's cross-compilation is first-class. To build for a different target from the host: + +```bash +# Build for Linux ARM64 on an x86_64 host (no QEMU needed) +zig build -Dtarget=aarch64-linux-musl -Doptimize=ReleaseSafe +``` + +In Docker with `docker buildx`: + +```bash +docker buildx build --platform=linux/arm64 \ + --build-arg BIN_NAME=myapp -t myapp -f Dockerfile.zig . +``` + +`--platform=$BUILDPLATFORM` on the builder stage keeps the Zig compiler itself running natively. Only the output binary is cross-compiled. + +## Upgrading Zig version + +1. Check the latest stable release at [ziglang.org/download](https://ziglang.org/download/). +2. Download the tarballs and verify them: + +```bash +curl -fsSL https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz | sha256sum +curl -fsSL https://ziglang.org/download/0.16.0/zig-aarch64-linux-0.16.0.tar.xz | sha256sum +``` + +1. Update `ARG ZIG_VERSION` and both SHA-256 constants in `Dockerfile.zig`, + `Dockerfile.zig.chainguard`, and `Dockerfile.devcontainer`. +1. Update `ZLS_VERSION` in `Dockerfile.devcontainer` and `devcontainer.json` + to match — ZLS must match the Zig version exactly. + +## Build and run + +```bash +# Distroless (default) +docker build --build-arg BIN_NAME=myapp -t myapp -f Dockerfile.zig . + +# Custom optimize mode +docker build --build-arg BIN_NAME=myapp --build-arg ZIG_OPTIMIZE=ReleaseSmall \ + -t myapp -f Dockerfile.zig . + +# Chainguard (free tier -- pin by digest in production) +docker build --build-arg BIN_NAME=myapp -t myapp -f Dockerfile.zig.chainguard . + +# Multi-arch +docker buildx build --platform=linux/amd64,linux/arm64 \ + --build-arg BIN_NAME=myapp -t myapp -f Dockerfile.zig . + +# Run (hardened) +docker run --rm \ + --read-only \ + --cap-drop=ALL \ + --security-opt=no-new-privileges \ + myapp +``` + +## Expected build-context layout + +```text +. +├── Dockerfile.zig # or Dockerfile.zig.chainguard +├── .dockerignore # provided (excludes zig-cache/, .zig-cache/, zig-out/) +├── build.zig # required: defines executable name and build options +├── build.zig.zon # optional: package manifest (fetched during build) +└── src/ + └── main.zig +``` + +> **BIN_NAME** must match the executable name declared in `build.zig` (`b.addExecutable(.{ .name = "" })`). + +## Chainguard digest-pinning + +The Chainguard free tier publishes only `:latest`. For reproducible builds, pin by digest: + +```bash +docker pull cgr.dev/chainguard/wolfi-base:latest +docker inspect --format='{{index .RepoDigests 0}}' cgr.dev/chainguard/wolfi-base:latest + +docker pull cgr.dev/chainguard/static:latest +docker inspect --format='{{index .RepoDigests 0}}' cgr.dev/chainguard/static:latest +``` + +Then replace the `FROM` lines with the digest form: + +```dockerfile +FROM cgr.dev/chainguard/wolfi-base@sha256: AS builder +# ... +FROM cgr.dev/chainguard/static@sha256: +``` + +## Devcontainer variant + +`Dockerfile.devcontainer` is based on `mcr.microsoft.com/devcontainers/base:1-bookworm` and adds: + +- Zig toolchain (pinned, SHA-256 verified) +- ZLS (Zig Language Server, matching Zig version) +- `gdb`, `lldb`, `valgrind` for debugging and memory analysis + +The companion `.devcontainer/devcontainer.json` wires up: + +- `ziglang.vscode-zig` extension (uses the installed ZLS and Zig binaries) +- `vadimcn.vscode-lldb` for native debugger support +- Format on save via ZLS +- `postCreateCommand: zig version && zls --version` to verify the install + +### Reopen in Container + +1. Open the `dockerfiles/zig/` folder in VS Code. +2. When prompted "Reopen in Container", click yes — or use `Ctrl+Shift+P` → **Dev Containers: Reopen in Container**. +3. VS Code builds `Dockerfile.devcontainer`, mounts the workspace, and installs extensions. + +## Linking against libc + +If your application calls C functions (e.g. via `@cImport`) and links glibc: + +```zig +// Forces dynamic glibc linkage +exe.linkLibC(); +``` + +Switch the runtime to `gcr.io/distroless/cc-debian12:nonroot` which includes glibc and libstdc++. Alternatively, target musl explicitly to keep the binary static: + +```zig +// build.zig +exe.target = b.resolveTargetQuery(.{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .musl, +}); +``` + +## Hardening checklist + +- [ ] Set `BIN_NAME` to the executable name declared in `build.zig`. +- [ ] Pin `ZIG_VERSION` (default `0.16.0`) and update both SHA-256 constants together. +- [ ] Commit `build.zig.zon` with exact dependency hashes — `zig build --fetch` verifies them. +- [ ] Use `ReleaseSafe` in production (bounds and overflow checks) unless benchmarks justify `ReleaseFast`. +- [ ] Build with `--platform` matching your deployment target architecture. +- [ ] Run with `--read-only`, `--cap-drop=ALL`, `--security-opt=no-new-privileges`. +- [ ] Set resource limits (`--memory`, `--cpus`) to bound runaway processes. +- [ ] Scan the built image (`grype`, `trivy`) before publishing. +- [ ] Sign the image and SLSA provenance with Cosign — see [`docs/supply-chain.md`](../../docs/supply-chain.md).