From abbab4d423b1135aba1577e894fafbc7fc974267 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 15 Jul 2026 01:50:50 +0200 Subject: [PATCH] security(docker): apt-get upgrade base OS in all runtime base stages (#10114) Co-authored-by: Claude Opus 4.8 (1M context) --- Dockerfile | 1 + docker/DockerfileSlim | 1 + docker/DockerfileSlimEe | 1 + docs/docker-security.md | 65 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 docs/docker-security.md diff --git a/Dockerfile b/Dockerfile index f6eb31b0a0..83bf4c8513 100644 --- a/Dockerfile +++ b/Dockerfile @@ -162,6 +162,7 @@ ENV PATH /usr/local/bin:/root/.local/bin:/tmp/.local/bin:$PATH RUN apt-get update \ + && apt-get upgrade -y \ && apt-get install -y --no-install-recommends netbase tzdata ca-certificates wget curl jq unzip build-essential unixodbc xmlsec1 tini gnupg libargon2-1 \ && if echo "$features" | grep -q "ee"; then apt-get install -y --no-install-recommends libsasl2-modules-gssapi-mit krb5-user; fi \ && apt-get clean \ diff --git a/docker/DockerfileSlim b/docker/DockerfileSlim index f7794f87bf..92138e8181 100644 --- a/docker/DockerfileSlim +++ b/docker/DockerfileSlim @@ -39,6 +39,7 @@ ENV PATH=/usr/local/bin:/root/.local/bin:/tmp/.local/bin:$PATH # Install system dependencies RUN apt-get update \ + && apt-get upgrade -y \ && apt-get install -y --no-install-recommends ca-certificates wget curl git jq unzip unixodbc xmlsec1 gnupg lsb-release libgnutls30t64 libgcrypt20 \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/docker/DockerfileSlimEe b/docker/DockerfileSlimEe index e460797f4e..63f121abb3 100644 --- a/docker/DockerfileSlimEe +++ b/docker/DockerfileSlimEe @@ -39,6 +39,7 @@ ENV PATH=/usr/local/bin:/root/.local/bin:/tmp/.local/bin:$PATH # Install system dependencies RUN apt-get update \ + && apt-get upgrade -y \ && apt-get install -y --no-install-recommends ca-certificates wget curl git jq unzip unixodbc xmlsec1 gnupg lsb-release libgnutls30t64 libgcrypt20 \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/docs/docker-security.md b/docs/docker-security.md new file mode 100644 index 0000000000..f0a9f23073 --- /dev/null +++ b/docs/docker-security.md @@ -0,0 +1,65 @@ +# Docker base-OS security patching + +The runtime images are built on `debian:trixie-slim` (Debian stable). The base +runtime stages run `apt-get update && apt-get upgrade -y && apt-get install …` +so that base-OS packages pick up Debian security and point-release fixes at +build time, instead of staying frozen at whatever versions the base tag shipped. + +## Where the upgrade lives + +`apt-get upgrade -y` is applied in the first apt block of the three stages that +establish a runtime Debian layer: + +- `Dockerfile` (the primary `windmill` / `windmill-ee` image) +- `docker/DockerfileSlim` (`windmill-slim`) +- `docker/DockerfileSlimEe` (`windmill-ee-slim`) + +Every other runtime image inherits its base OS from one of these transitively, +so patching here is sufficient: + +- `DockerfileFull`, `DockerfileFullEe`, `DockerfileCuda` build `FROM` the primary + `windmill` / `windmill-ee` image. +- `DockerfileExtra` builds `FROM windmill-ee-slim`. + +The nsjail *builder* stages are throwaway (only the compiled `nsjail` binary is +copied out), so they are intentionally not upgraded. `DockerfileMultiplayer` +(`node:slim`), the CLI, Caddy-L4, CUDA-only, and RHEL/dnf images are out of scope +for this apt-based patching. + +## Why `apt-get upgrade` and not `unattended-upgrades` / pinning + +Debian stable's archive only receives security updates and ABI-stable point +releases (e.g. `openssl 3.0.x → 3.0.x+deb12u2`, same soname). It does not ship +feature/major bumps, so a build-time `apt-get upgrade` cannot silently break a +pinned runtime dependency the way it might on a rolling distro. The default +`debian.sources` already includes the `*-security` suite, so a plain upgrade +picks up security fixes without extra machinery. `unattended-upgrades` adds a +package and config for no benefit in a build context (it does not run at build +time), and a pinned base digest would freeze the CVEs in place. + +Note the runtime apt installs were already unpinned (only the throwaway nsjail +builder pins versions), so these images were never byte-for-byte reproducible in +this dimension; `upgrade` moves the same already-floating packages to their +patched versions rather than changing the reproducibility posture. + +## Caching and freshness + +`apt-get upgrade` sits in the same `RUN` as `apt-get update && install`. Docker +keys that layer on the command string plus the parent layer, not on package +contents, so an unchanged build is a cache hit and the upgrade does not re-run. +The layer is invalidated — and fresh patches are pulled — when the parent layer +changes, primarily when the mutable `debian:trixie-slim` base digest moves on a +Debian point release. That self-aligns: the cache refreshes when there is +something new to pick up. + +Because of apt-cache staleness, a security fix that lands between base-digest +bumps will not be picked up by a cached build until the next bump. To close that +gap, rebuild and republish the `latest` / patch tags: + +- on each Debian point release (base digest bump), and +- on a periodic cadence (e.g. per Windmill release), rebuilding the base stages + with `--no-cache` if you need to force a fresh `apt-get upgrade` regardless of + the base digest. + +Scan the published images (e.g. Trivy / Defender) after rebuilds to confirm the +base-OS finding count stays low.