Chainstack build of nginx-ingress-controller, with custom nginx modules baked inside.
Modules:
ingress-nginx was retired upstream on 2026-03-24, so no patched controller release will ever ship again. This image therefore builds nginx itself rather than inheriting it from the upstream controller image — that is the only way to pick up nginx security fixes now.
Upstream's build tree is fetched at build time from its pinned, archived tag rather
than vendored into this repository, so this stays a Dockerfile-only project. The two
modifications we make to that tree are the two commands in the upstream-src stage of
the Dockerfile:
NGINX_VERSION (currently to 1.30.4, for CVE-2026-42533)19_*log_escape_non_ascii.patch — 2 of its 10 hunks no longer apply to
1.30.4, and the directive it adds is referenced nowhere in the controller, so
rebasing it would carry a patch nothing uses. 31 of the 32 patches apply unchanged.ngx_devel_kit with patches-ndk/ndk-complex-value-end.patch. nginx 1.30.4
moved the rewrite-engine stack push into a new complex_value_end code as part of the
CVE fix; NDK's private copy of ngx_http_rewrite_value() never gained it, so every
set_* directive with a variable argument (the controller emits set_escape_uri for
auth-signin ingresses) segfaults the worker. Upstream NDK is unfixed. This caused
the 2026-09-08 prod-console SSO outage.All are wrapped in grep/test guards. Without them an upstream layout change would
make sed match nothing and the build would ship a vulnerable nginx while the tag
and image labels claimed otherwise. The guards turn that into a failed build.
The smoke stage then starts the freshly built nginx with the auth-signin constructs
and a set_by_lua_block, sends real requests, and fails the build on a worker crash or on
any value that differs from what nginx 1.25.5 returned (smoke/expected.txt). Compiling
a third-party module against a new nginx proves nothing about whether it still works.
The Go controller is not rebuilt — its source is unchanged, so the published
upstream binaries (dbg, nginx-ingress-controller, wait-shutdown) are relayered
onto the patched nginx.
/etc is taken from upstream source, not from the published controller image,
whose /etc/nginx/modules/*.so are compiled against the old nginx and are not binary
compatible with the new one.
Everything is pinned as an ARG at the top of the Dockerfile:
| Component | Version |
|---|---|
| nginx | 1.30.4 |
| ingress-nginx controller | v1.11.5 |
| ngx_http_websocket_stat_module | v3.0.5 |
Tag scheme is <controller>-<ws-module>-nginx<nginx>, e.g. 1.11.5-3.0.5-nginx1.30.4.
nginx gained its own component because it now moves independently of the controller.
docker build --platform linux/amd64 -t chainstack/ingress-nginx:dev .
amd64 only — that is the only architecture published, and the runtime linker path
(/etc/ld-musl-x86_64.path) is derived from the build host's architecture.
The build compiles nginx, LuaJIT, ModSecurity and opentelemetry-cpp from source, so expect roughly 20 minutes rather than the ~1 minute the previous single-module build took.
./hack-patch-probe.sh 1.31.3 # does upstream's patch set still apply?
Run this first. It answers in ~30s what a full build takes ~20min to reveal. Then edit
ARG NGINX_VERSION in the Dockerfile.
If a patch fails, check whether it is still needed before rebasing it — several are
backports of fixes that newer nginx already contains upstream (28_*CVE-2025-23419*
is one, on the v1.13.0 tree).
Docker Hub autobuild is wired to this repository: merging to master rebuilds
latest, and creating a release tagged vX publishes Docker tag X (the leading v
is stripped). No manual docker push is involved.
nginx -t fails as www-data with open() "/run/nginx.pid" failed (13: Permission denied). Pre-existing, and harmless in production because the controller renders its
own pid path. Add pid /tmp/test.pid; to throwaway test configs.
ws_log_format accepts only this module's own variables ($ws_opcode,
$ws_payload_size, $ws_message_size, $ws_packet_source, $ws_conn_age). Putting a
standard nginx variable such as $uri or $status in it crashes the worker with
SIGSEGV — the module does not validate the format string.
Content type
Image
Digest
sha256:6aa458f29…
Size
101.3 MB
Last updated
19 days ago
docker pull chainstack/ingress-nginx