mathiasror/varde
GitHub: mathiasror/varde
一套基于 Nix 构建的极简无发行版容器基础镜像,为多种语言运行时和常见服务提供最小化、非 root 运行且附带 SBOM 与签名证明的镜像。
Stars: 0 | Forks: 0
# varde
使用 Nix 构建的极简、**无发行版** (distroless) 容器基础镜像。每个镜像仅包含应用运行所需的内容 —— 语言运行时(或者对于编译型语言,仅包含二进制文件所链接的库),没有其他任何东西:**没有 shell,没有包管理器,没有 coreutils,也没有 busybox** —— 且每个镜像都以非特权用户身份运行。在有 C 库可供选择的情况下,镜像默认使用 **musl**(更小的代码库和 CVE 记录),只需更改一个 tag 即可切换至 **glibc**。
## 镜像
**语言运行时**
| 镜像 | Tags | 运行环境 | 插入式契约 (Drop-in contract) |
| --- | --- | --- | --- |
| `varde-jre` | `17`* `21` `25` `latest`(=21) | JVM 应用 (Java/Kotlin) | `COPY app.jar /app/app.jar` (fat/boot jar) |
| `varde-python` | `3.11` `3.12` `3.13` `latest`(=3.13) | Python 应用 | 将解释器依赖及代码 `COPY` 到 `/app` 中 |
| `varde-node` | `22` `24` `latest`(=24) | Node.js 应用 | 将 `node_modules` 及代码 `COPY` 到 `/app` 中 |
| `varde-php` | `8.3` `8.4` `8.5` `latest`(=8.5) | PHP 应用 (cli + `php-fpm`) | 将代码及 `vendor/` `COPY` 到 `/app` 中 |
**服务**
| 镜像 | Tags | 运行环境 | 插入式契约 (Drop-in contract) |
| --- | --- | --- | --- |
| `varde-nginx` | `latest` | 静态站点 (非 root,监听 `:8080`) | `COPY site/ /app/` |
| `varde-redis` | `latest` | Redis server | 直接运行,或通过 `CMD` 传入 `redis.conf` |
| `varde-postgres` | `16` `17` `18` `latest`(=18) | PostgreSQL server | 通过 `--entrypoint` 对挂载在 `/app` 的 volume 进行一次性 `initdb`,随后运行 |
| `varde-mysql` | `8.4`* `latest`(=8.4) | MySQL 8.4 LTS server | 运行一次 `--initialize-insecure`,随后直接运行 |
| `varde-rabbitmq` | `latest` | RabbitMQ broker (无 shell 的 Erlang 启动) | 直接运行;通过 `COPY` 将 `rabbitmq.conf` 和 `enabled_plugins` 复制到 `/app` |
| `varde-memcached` | `latest` | Memcached server | 直接运行 (`:11211`),或通过 `CMD` 传递 flags,例如 `CMD ["-m", "256"]` |
**编译型二进制基础镜像** (将你自己的二进制文件放在 `/app/app`)
| 镜像 | Tags | 运行环境 |
| --- | --- | --- |
| `varde-static` | `latest` | 任何**静态**二进制文件 — Go `CGO_ENABLED=0`、musl-static Rust、静态 C/Zig |
| `varde-glibc` | `latest` | 任何**glibc 动态**二进制文件 — 默认的 `cargo build`、cgo Go、C/C++ |
| `varde-musl` | `latest` | 任何**musl 动态**二进制文件 |
`varde-go` 和 `varde-rust` 作为**别名**发布,以提高可发现性:
`varde-go` → `varde-static`,`varde-rust` → `varde-glibc`(具有相同的摘要)。
每个镜像都发布在 **GHCR** 下的 `ghcr.io/mathiasror/`,支持多架构 (`amd64` + `arm64`)。
### libc:默认使用 musl,按需使用 glibc
每个运行时/服务镜像都是针对两种 C 库构建的。**裸 tag 是 musl**(默认);追加 **`-glibc`** 以选择使用 glibc:
```
FROM ghcr.io/mathiasror/varde-python:3.13 # musl (default)
FROM ghcr.io/mathiasror/varde-python:3.13-glibc # glibc (opt-in)
```
默认使用 musl,因为更小的 libc 意味着更少的补丁和更小的攻击面。当某些组件需要时,请使用 `-glibc` —— 例如仅包含 manylinux (glibc) 二进制文件的 wheel、基于 glibc 构建的原生 addon,或是假定存在 glibc 的预编译依赖。针对特定架构的 tag 包含 libc 标识,例如 `:21-musl-arm64` 或 `:3.13-glibc-amd64`。
*`varde-jre:17` **仅限 glibc** —— Adoptium 没有提供用于 JDK 17 的 aarch64 Alpine/musl JRE;`:21` 和 `:25` 默认使用 musl。`varde-mysql` **仅限 glibc** —— 无法从 nixpkgs 构建 musl MySQL(其 protobuf/abseil 锁定版本在 musl 上被标记为损坏),因此其裸 tag 就是 glibc 构建版本。`varde-static` 不包含 libc(静态二进制文件嵌入了自己的库),因此它没有 `-musl`/`-glibc` 之分。
`varde-postgres` 是**“无 shell”规则的一个例外**:它提供了一个最小化的静态 `ash`(位于 `/bin/sh`)(这是 Nix 自己的 busybox-sandbox-shell —— 一个单一的二进制文件,不包含其他 applet),因为 PostgreSQL 的 `initdb` 需要通过 libc 的 `popen`/`system` 进行引导,而这些操作硬编码了 `/bin/sh`。其他所有镜像都保持无 shell 状态。
### 每个镜像均保证
| 属性 | 值 |
| --- | --- |
| 用户 | `1000:1000` (`app`, 非 root) |
| 工作目录 | `/app` (由 `1000:1000` 拥有) |
| Shell / 包管理器 | **无** |
| TLS | 位于 `/etc/ssl/certs/ca-certificates.crt` 的 CA 证书包 (设置了 `SSL_CERT_FILE`) |
| 时区 | 位于 `/usr/share/zoneinfo` 的 tzdata (设置了 `TZDIR`) |
| 可写路径 | `/app`, `/tmp` (sticky) |
| 扫描 | 每个镜像均提供用于 Trivy 的 CycloneDX SBOM (见下文) |
加载**外部编译**原生代码(`python`、`node` 以及 `glibc`/`musl` 基础镜像)的镜像还会在标准 FHS 路径(`/lib`, `/lib64`)下附带该 libc 的 loader + `libstdc++`/`libgcc_s`,以便 manylinux/musllinux wheels、原生 node addons 以及常规的 `cargo`/`gcc` 二进制文件能找到它们的 loader 和库。
`varde-static` 类似于 scratch(静态二进制文件不需要这些)。
## 使用镜像
集成的过程就是“把你的应用放进去”。由于没有 shell,entrypoint 会直接运行你的应用 —— 你需要准备一个自包含的构件(一个 fat JAR、一个二进制文件,或是代码加上已安装的依赖)。完整且可构建的示例位于 [`examples/`](examples/) —— 每个示例都接收一个 `ARG BASE_IMAGE`,因此你可以使用任何 tag 对其进行 `docker build`:
- **JVM** — [`examples/jre/spring-boot-gradle/`](examples/jre/spring-boot-gradle/) (Spring Boot, Gradle `bootJar`)
- **Python** — [`simple`](examples/python/simple/), [`requirements-venv`](examples/python/requirements-venv/), [`uv`](examples/python/uv/)
- **Node** — [`simple`](examples/node/simple/), [`express`](examples/node/express/) (production-shaped)
- **Go** — [`examples/go/simple/`](examples/go/simple/) (static, on `varde-static`)
- **Rust** — [`examples/rust/simple/`](examples/rust/simple/) (glibc, on `varde-glibc`)
- **PHP** — [`examples/php/simple/`](examples/php/simple/) (`php-fpm` behind nginx, or cli)
- **nginx** — [`examples/nginx/static-site/`](examples/nginx/static-site/)
- **redis** — [`examples/redis/simple/`](examples/redis/simple/)
- **postgres** — [`examples/postgres/simple/`](examples/postgres/simple/) (explicit `initdb`, volume at `/app`)
- **mysql** — [`examples/mysql/simple/`](examples/mysql/simple/) (`--initialize-insecure`, then serve)
- **rabbitmq** — [`examples/rabbitmq/simple/`](examples/rabbitmq/simple/) (config + plugins via `COPY`)
- **memcached** — [`examples/memcached/simple/`](examples/memcached/simple/)
最简短的用例(一个静态二进制文件):
```
FROM ghcr.io/mathiasror/varde-static:latest # a.k.a. varde-go
COPY app /app/app
# Inherits USER 1000:1000, WORKDIR /app, ENTRYPOINT ["/app/app"]
```
在生产环境中请固定使用某个摘要 (`...@sha256:…`)。如果需要,请使用特定架构的 tag (`:21-musl-arm64`) 明确指定架构。
## 构建
镜像仅支持 Linux,并在原生 runner 上构建。此仓库**也可以在 macOS 主机上**构建,但必须通过 Linux builder —— Nix 无法直接从 Darwin 生成 Linux 镜像(请使用 nix-darwin 的 [`linux-builder`](https://nixos.org/manual/nixpkgs/stable/#sec-darwin-builder),或者直接让 CI 来构建)。在 Linux 主机上:
```
nix build .#image-jre-21-musl # -> ./result (a Docker-format image tarball)
nix build .#image-python-3_12-glibc # note: dots -> underscores, libc is part of the attr
docker load < result
# or push to your own namespace without a daemon:
skopeo copy docker-archive:result docker://ghcr.io//varde-jre:21-musl-amd64
```
列出所有可用内容:`nix eval --json .#ciMatrix`(每个条目都包含其 `libc`);`nix eval --json .#imageAliases` 显示 go/rust 的别名。
## 漏洞扫描 (Trivy)
无发行版镜像**没有 OS 包数据库**,因此仅运行 `trivy image` 不会报告任何系统包。我们利用 Nix 的精确闭包来弥补这一差距:对于每个镜像,[`sbomnix`](https://github.com/tiiuae/sbomnix) 都会生成一个**包含 CPE 的 CycloneDX SBOM**,涵盖系统包(glibc/musl、zlib 等)和语言运行时。
```
# System packages + runtime, via the SBOM:
nix run .#sbom-jre-21-musl -- sbom.cdx.json
trivy sbom sbom.cdx.json
# Your app's own dependencies, once the app image is built:
trivy image ghcr.io/mathiasror/your-app:tag
```
`trivy image` 原生支持从 JAR、`node_modules`、Python dist-info 以及嵌入在 **Go 和 Rust 二进制文件**中的构建信息读取应用依赖 —— 因此最终的应用镜像也是可扫描的。CI 会在每次构建时运行这两项扫描;发现的问题会记录在构建日志中,SBOM 会作为构建构件上传。对于这些基础镜像,扫描**仅用于生成报告**(因此在发布时它们会带着可见的发现结果);请在你的*应用*镜像中强制执行严格的拦截规则,因为在那里你可以控制依赖集。
## 供应链:签名、SBOM 与出处
每个发布的镜像均使用 [cosign](https://docs.sigstore.dev/) 通过 GitHub Actions OIDC 身份进行**无密钥签名 (keyless-signed)** —— 不存在长期有效的密钥 —— 并且每个特定架构的镜像都带有一份 **CycloneDX SBOM 证明**(即上文的闭包 SBOM,它被附加到镜像上,与镜像一同传输,而不仅仅是 CI 构件)以及一份 **SLSA 构建出处证明**(builder、source commit、invocation)。签名和证明记录在公共的 Rekor 透明度日志中;多架构 manifest 列表也经过了签名,因此通过 tag 进行验证是有效的。
```
# Verify a signature (the identity is this repo's build.yml workflow):
cosign verify \
--certificate-identity-regexp '^https://github.com/mathiasror/varde/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/mathiasror/varde-jre:21
# Provenance and SBOM attestations live on the per-arch digests (the things
# actually built) — resolve a platform digest first:
d=$(crane digest ghcr.io/mathiasror/varde-jre:21 --platform linux/amd64)
# Verify SLSA build provenance (built by this repo's workflow):
gh attestation verify "oci://ghcr.io/mathiasror/varde-jre@${d}" --owner mathiasror
# Verify the CycloneDX SBOM attestation (and extract the SBOM):
cosign verify-attestation --type cyclonedx \
--certificate-identity-regexp '^https://github.com/mathiasror/varde/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
"ghcr.io/mathiasror/varde-jre@${d}" \
| jq -r '.payload | @base64d | fromjson | .predicate' > sbom.cdx.json
```
## CI
[`.github/workflows/build.yml`](.github/workflows/build.yml) 的数据完全由 flake 驱动:
1. **setup** — 读取 `.#ciMatrix`、`.#latestTags` 和 `.#imageAliases`,将每个镜像/变体(包括 libc)与 `amd64` + `arm64` 交叉组合。
2. **build** — 原生 runner(`ubuntu-latest` / `ubuntu-24.04-arm`):构建镜像,生成并使用 Trivy 扫描 SBOM,扫描镜像,进行冒烟测试,将特定架构的 tag 推送到 GHCR(在 PR 中跳过)。
3. **manifest** — 根据 libc tag 组装多架构列表、裸版本和 `:latest` / `:latest-glibc` 别名,以及 `varde-go` / `varde-rust` 的别名摘要。
[`.github/workflows/e2e.yml`](.github/workflows/e2e.yml) 在本地构建的基础镜像之上构建每个示例,对应用的运行进行冒烟测试,并断言镜像大小预算([`scripts/e2e.sh`](scripts/e2e.sh))。
`build` 和 `e2e` 都会推送/拉取一个共享的 **Cachix** 缓存,因此从源码构建的 musl 变体只需编译一次,即可跨作业、后续运行以及 e2e 工作流重复使用(第一次运行会很慢;随后的运行都是下载过程)。
身份验证内置的 `GITHUB_TOKEN`。每周的定时任务会针对最新的 nixpkgs 进行重新构建,这样即使此仓库未发生更改,已发布的镜像也能获得 CVE 修复。
## 添加新镜像
只需添加 `images/.nix` —— flake 会自动发现它,CI 会拾取它而无需更改工作流。运行时/服务模块通过 `vardeLib.mkVariants` 将每个版本与 libc 轴交叉组合:
```
{ pkgs, vardeLib, lib }:
{
description = "…";
latest = ""; # bare/:latest resolves to musl if built, else glibc
variants = vardeLib.mkVariants pkgs {
versions = {
# `spec` is a function of the libc's package set (`pkgs` for glibc,
# `pkgs.pkgsMusl` for musl), so the runtime is built for each libc. Each
# "" becomes "-musl" and "-glibc". Restrict `libcs` to skip one.
"" = {
# libcs = [ "glibc" ]; # optional: e.g. no musl build upstream
spec = p: {
contents = [ (vardeLib.relocate p "…" "runtime" p.) ];
entrypoint = [ "/runtime/bin/…" ];
cmd = [ … ]; # optional
env = [ "PATH=/runtime/bin" ];
fhs = false; # true => add the libc's loader + libs for external native code
};
};
};
};
}
```
编译型二进制基础镜像只需一行代码 —— 重用共享规格:
```
{ pkgs, vardeLib, lib }:
{
description = "…";
latest = "latest";
variants."latest" = vardeLib.staticSpec; # or vardeLib.glibcSpec / vardeLib.muslSpec
}
```
框架免费提供了非 root 用户、CA 证书、tzdata、`/app`、`/tmp`、通用环境变量、labels 以及 SBOM 应用。Helper 位于 [`lib/default.nix`](lib/default.nix)(`relocate`、`mkLibcEnv`、`mkVariants`、`buildImage`、`buildSbomApp`)。有关 libc 轴模块的实际示例,请参阅 [`images/python.nix`](images/python.nix);有关服务示例,请参阅 [`images/nginx.nix`](images/nginx.nix)。
要将某个名称发布为另一个镜像的别名,请将其添加到 [`flake.nix`](flake.nix) 的 `imageAliases` 中。
## 布局
```
flake.nix # auto-discovers images/*.nix; packages, sbom apps, ciMatrix, imageAliases
lib/default.nix # scaffolding + mkLibcEnv, mkVariants, buildImage, buildSbomApp
images/.nix # one module per image (+ images/nginx.conf)
examples/// # buildable "drop your app in" examples
scripts/e2e.sh # build examples on local bases, smoke + size budgets
.github/workflows/build.yml # data-driven matrix build -> Trivy -> GHCR
.github/workflows/e2e.yml # build + smoke-test the examples
.github/workflows/pages.yml # deploy the landing page to GitHub Pages
site/ # landing page (static, no build step)
docs/design.md # design notes
LICENSE # Apache-2.0
```
## 许可证
varde 自身的构建定义 —— Nix flake、`images/` 下的镜像模块、共享库、工作流、示例和站点 —— 均获得 [Apache License 2.0](LICENSE) 的许可。
每个发布的镜像还根据各自的许可证捆绑了第三方软件:它打包的语言运行时、服务器和系统库(例如 glibc 或 musl、Temurin JRE、CPython、Node.js、nginx、Redis 及其依赖项)均根据其各自的上游许可证进行分发。`nix build` 会记录确切的闭包,生成的 SBOM 会列出每个组件,因此所有内容始终是可检查的。
标签:Distroless, Nix, 安全基线, 容器镜像, 教学环境, 请求拦截