CtrlK
BlogDocsLog inGet started
Tessl Logo

nic-docker-images

Docker image build system, Dockerfile structure, image variants, build scripts, and Makefile targets for NIC. Use when building container images, modifying the Dockerfile, adding new image variants, debugging image builds, or working with build scripts.

69

Quality

84%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

SKILL.md
Quality
Evals
Security

NIC Docker Image Build System

Dockerfile Architecture

Single build/Dockerfile (~1100 lines), heavily multi-stage. The BUILD_OS arg selects which base image stage is used, and TARGET selects the final stage.

nginx-files (scratch)           <- Repo files, signing keys, package repo definitions, scripts
  |                                (rewrites repo hosts + user-agent strings via sed)
OS-specific base stages         <- One per variant (debian, alpine, ubi, *-plus, *-nap[-v5][-fips][-agent])
  |
FROM ${BUILD_OS} AS common      <- Runs common.sh + patch-os.sh, sets permissions
  |
TARGET stages (final image)     <- local, container, goreleaser, debug, debug-container,
                                   download, aws, patched, and *-prebuilt variants

Supporting images built outside this Dockerfile:

  • build/dependencies/Dockerfile.ubi10 builds the UBI package image published to ghcr.io/nginx/dependencies/nginx-ubi, consumed via the UBI10_PACKAGES_IMAGE build arg (pinned by digest in the Makefile). Built by build-ubi-dependency.yml on changes to that file, or locally with make ubi10-dependency-image-local (requires rhel_license).
  • build/dependencies/tracking.info.default is copied in as tracking.info to attribute the install to NIC before the Plus licence reporter initialises.

Image Variants

make all-images builds 25 variants. Three axes combine:

  1. OS family: Debian 13 (trixie), Alpine 3.24, UBI 10 minimal
  2. NGINX edition: OSS or Plus, plus optional NAP WAF v4 / WAF v5 / DoS and FIPS on Alpine
  3. NGINX Agent version: v2 (default on NAP variants) or v3 (-agent suffixed stages)
OSOSSPlusPlus+WAFPlus+WAFv5Plus+DoSPlus+WAF+DoSPlus+FIPSPlus+WAF+FIPSPlus+WAFv5+FIPS
Debianyesyesyesyesyesyes---
Alpineyesyes----yesyesyes
UBI 10yesyesyesyesyesyes---

Architecture: amd64 + arm64 for OSS and Plus. NAP variants are amd64 only.

Agent v2 vs v3 -- easy to miss

  • OSS and plain Plus images ship nginx-agent v3 only (AGENT_V3_VERSION).
  • NAP WAF stages exist in pairs: the unsuffixed stage pins AGENT_V2_VERSION, the -agent suffixed stage pins AGENT_V3_VERSION. For example debian-plus-nap (agent v2) and debian-plus-nap-agent (agent v3).
  • .github/data/matrix-images-nap.json builds both halves of each pair against all three nap_modules values, so CI covers DoS-only on agent v2 and v3.
  • The local Makefile targets do not mirror the matrix exactly. The DoS-only targets debian-image-dos-plus and ubi-image-dos-plus build the -agent (v3) stages and have no agent-v2 twin. Read the target body before assuming a naming pattern.
  • Python e2e tests distinguish the two agents with the agentv2 / agentv3 pytest markers.

Makefile Image Targets

All targets call $(DOCKER_CMD) = docker build --platform linux/$(ARCH) --target $(TARGET) -f build/Dockerfile.

TargetBUILD_OSNAP_MODULESAgent
debian-imagedebian-v3
alpine-imagealpine-v3
ubi-imageubi-v3
debian-image-plusdebian-plus-v3
alpine-image-plusalpine-plus-v3
alpine-image-plus-fipsalpine-plus-fips-v3
ubi-image-plusubi-10-plus-v3
alpine-image-nap-plus-fipsalpine-plus-nap-fipswafv2
alpine-image-nap-plus-fips-agentalpine-plus-nap-fips-agentwafv3
alpine-image-nap-v5-plus-fipsalpine-plus-nap-v5-fipswafv2
alpine-image-nap-v5-plus-fips-agentalpine-plus-nap-v5-fips-agentwafv3
debian-image-nap-plusdebian-plus-napwafv2
debian-image-nap-plus-agentdebian-plus-nap-agentwafv3
debian-image-nap-v5-plusdebian-plus-nap-v5wafv2
debian-image-nap-v5-plus-agentdebian-plus-nap-v5-agentwafv3
debian-image-dos-plusdebian-plus-nap-agentdosv3
debian-image-nap-dos-plusdebian-plus-napwaf,dosv2
debian-image-nap-dos-plus-agentdebian-plus-nap-agentwaf,dosv3
ubi-image-nap-plusubi-10-plus-napwafv2
ubi-image-nap-plus-agentubi-10-plus-nap-agentwafv3
ubi-image-nap-v5-plusubi-10-plus-nap-v5wafv2
ubi-image-nap-v5-plus-agentubi-10-plus-nap-v5-agentwafv3
ubi-image-dos-plusubi-10-plus-nap-agentdosv3
ubi-image-nap-dos-plusubi-10-plus-napwaf,dosv2
ubi-image-nap-dos-plus-agentubi-10-plus-nap-agentwaf,dosv3

Other targets: all-images (all 25, prunes the builder cache first), ubi10-dependency-image-local (UBI package image), push (docker push to PREFIX:TAG), patch-os (OS patches an existing image).

Plus images receive $(PLUS_ARGS): --secret id=nginx-repo.crt --secret id=nginx-repo.key plus NGINX_PLUS_VERSION and PLUS_PACKAGE_REPO.

TARGET Variable

make build accepts only local, container, download, goreleaser, debug and errors on anything else. The remaining stages are selected by CI or by make patch-os directly.

TargetUse Case
localDefault -- binary pre-built on host, copied in
containerBinary built inside Docker (multi-arch capable)
goreleaserBinary from GoReleaser dist/ (CI builds)
debugIncludes delve debugger
debug-containerDelve build with the binary compiled inside Docker
downloadExtracts binary from a published Docker Hub image (DOWNLOAD_TAG, resolved by hack/docker.sh)
awsAWS marketplace variant
patchedOS patches an existing image (IMAGE_NAME)
local-prebuilt, goreleaser-prebuilt, aws-prebuiltSame as their counterparts but layered onto PREBUILT_BASE_IMG instead of rebuilding the OS stage

Key Build Args

ArgPurposeSource of truth
BUILD_OSBase image stageMakefile targets + Dockerfile stages
IC_VERSIONIngress controller version.github/data/version.txt
NGINX_PLUS_VERSIONNGINX Plus versionMakefile
NGINX_OSS_VERSIONNGINX OSS versionMakefile
AGENT_V2_VERSION / AGENT_V3_VERSIONnginx-agent version per variantMakefile
NAP_MODULESApp Protect modulesAny of waf, dos, or waf,dos
NAP_WAF_VERSION, NAP_WAF_COMMON_VERSION, NAP_WAF_PLUGIN_VERSION, NAP_WAF_IPI_VERSIONNAP WAF package pinsMakefile
UBI10_PACKAGES_IMAGEUBI package image, digest-pinnedMakefile (ghcr.io/nginx/dependencies/nginx-ubi)
PREBUILT_BASE_IMGBase for *-prebuilt targetsGCR image ref (set by CI)
IMAGE_NAMEImage to patch for TARGET=patchedmake patch-os / patch-image.yml
DOWNLOAD_TAGPublished tag for TARGET=downloadhack/docker.sh
OSS_PACKAGE_REPORepo host for OSS packagesMakefile / CI inputs (defaults to packages.nginx.org)
AGENT_PACKAGE_REPORepo host for NGINX Agent packagesMakefile / CI inputs (defaults to packages.nginx.org)
PLUS_PACKAGE_REPORepo host for Plus packagesMakefile / CI inputs (defaults to pkgs.nginx.com)
WAF_PACKAGE_REPORepo host for F5 NAP WAF packagesMakefile / CI inputs (defaults to pkgs.nginx.com)
DOS_PACKAGE_REPORepo host for F5 NAP DoS packagesMakefile / CI inputs (defaults to pkgs.nginx.com)

Do not hard-code IC_VERSION, NGINX_*_VERSION, AGENT_*_VERSION or NAP_WAF_* values in this file or in other docs as they change every release. Always reference the Makefile variables or .github/data/version.txt.


Build Scripts (build/scripts/)

ScriptPurpose
common.shSets up directories, copies NGINX templates (v1/v2), sets file permissions (101:0), runs setcap on nginx binaries
agent.shConfigures nginx-agent ownership; creates NMS compiler symlinks for NAP v4
nap-waf.shCreates WAF directories (/etc/nginx/waf/nac-policies, /opt/app_protect/)
nap-dos.shCreates DoS directories (/root/app_protect_dos, /shared/cores)
ubi-setup.shUBI-specific: installs shadow-utils, creates nginx user/group (101:0)
ubi-clean.shUBI-specific: removes build-time packages, cleans dnf cache

patch-os.sh is not in this repo -- the nginx-files stage fetches it from nginx/k8s-common (files/patch-os.sh). Changes to it must be made in that repository.

Package repo definitions (*.repo, *.sources, 90pkgs-nginx) are likewise fetched in nginx-files from cs.nginx.com and nginx/k8s-common, then rewritten with sed to inject the version, the *_PACKAGE_REPO host override, and the k8s-ic-<IC_VERSION> user-agent used for usage attribution.


Key Conventions

  • All images run as UID 101 (nginx user), with setcap cap_net_bind_service for ports 80/443
  • Docker BuildKit always enabled: uses --mount=type=bind, --mount=type=secret, --mount=type=cache
  • Plus credentials use --secret mounts, never COPY into layers
  • OSS builds use optional --secret mounts (required=false) for nginx-repo.crt/nginx-repo.key to authenticate against non-default repo hosts like pkgs-test.nginx.com when running authenticated CI builds.
  • Fixed upstream base images use pinned @sha256: digests for reproducibility; some stages intentionally use build-arg/tag-selected bases (for example BUILD_OS, UBI10_PACKAGES_IMAGE, or download/prebuilt images)
  • FIPS variants build on ghcr.io/nginx/alpine-fips; UBI variants build on redhat/ubi10-minimal
  • All images include nginx-module-otel (OpenTelemetry) and nginx-agent (usage reporting)
  • Plus images add njs and fips-check modules
  • Renovate manages base image digests and tool versions via # renovate: comments

Gotchas

  • Never store Plus credentials in image layers -- always use --secret mounts
  • Never add arm64 to NAP image matrices -- NAP is amd64 only
  • Always use BUILD_OS to select variants, not separate Dockerfiles
  • Add or change NAP WAF stages in pairs. A new foo-nap stage needs a matching foo-nap-agent stage, a Makefile target, an entry in make all-images, and both entries in .github/data/matrix-images-nap.json. The DoS-only Makefile targets are the documented exception -- they build the -agent stages only
  • The common stage unifies all variants -- changes there affect every image
  • common.sh detects Plus via BUILD_OS containing "plus" and creates OIDC directories
  • patch-os.sh lives in nginx/k8s-common, not build/scripts/ -- editing it here is impossible
  • UBI10_PACKAGES_IMAGE is digest-pinned in the Makefile; rebuilding build/dependencies/Dockerfile.ubi10 requires bumping that digest afterwards
  • When adding new image dependencies, update the relevant OS-specific stage AND the common stage if needed
Repository
nginx/kubernetes-ingress
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.