Container build-time techniques

Traditional container images inherit components, vulnerabilities, and attack surface from their base image (Debian, Alpine, Ubuntu). Even "minimal" base images include package managers, shells, interpreters, and system utilities that most applications never use. Each unnecessary component increases attack surface and introduces potential vulnerabilities.

ARMORRED eliminates base image inheritance by building FROM scratch. Only the components explicitly required for the application to function are included in the final image. This approach achieves dramatic reductions in size, component count, and vulnerability exposure.

Building from Scratch

Building from scratch means the container image starts as an empty filesystem. No distribution base layer. No package manager. No shell. Nothing except what the build process explicitly places into the image.

Traditional Dockerfile pattern:

FROM debian:12
RUN apt-get update && apt-get install -y nginx
...

ARMORRED pattern with Nix:

FROM scratch
COPY /nix/store/.../nginx /nix/store/.../nginx
COPY /nix/store/.../glibc /nix/store/.../glibc
COPY /nix/store/.../openssl /nix/store/.../openssl
...

Nix computes the transitive closure of dependencies required for nginx to execute and copies only those exact paths into the container. The result is a minimal, reproducible image with zero inherited components.

Component Reduction

The difference between base-image inheritance and scratch-based builds is substantial. For nginx 1.26:

MetricUpstream nginx:1.26.3ARMORRED HardenedARMORRED Locked
Base imagedebian:12-slimscratchscratch
Components150 packages19 packages20 packages
Reduction87.3%86.7%

Component breakdown:


$ syft packages docker.io/library/nginx:1.26.3 -o json | jq '.artifacts | length'
150

$ syft packages ghcr.io/armorred/nginx:1.26-hardened -o json | jq '.artifacts | length'
19

$ syft packages ghcr.io/armorred/nginx:1.26-locked -o json | jq '.artifacts | length'
20

Fewer components means:

  • Reduced attack surface: fewer binaries and libraries that could contain vulnerabilities
  • Faster vulnerability scanning: fewer components to correlate against CVE databases
  • Simplified incident response: smaller SBOM makes impact assessment faster
  • Smaller supply chain risk: fewer dependencies means fewer upstream sources to trust

Size Reduction

Minimal component sets produce significantly smaller images. For nginx 1.26:

MetricUpstreamARMORRED HardenedARMORRED Locked
Image size196 MB121 MB72 MB
Reduction38.5% (75 MB saved)63.1% (124 MB saved)
Layers72223


$ podman images
REPOSITORY TAG SIZE
docker.io/library/nginx 1.26.3 196 MB
ghcr.io/armorred/nginx 1.26-hardened 121 MB
ghcr.io/armorred/nginx 1.26-locked 72.3 MB

Smaller images provide operational benefits:

  • Faster pull times in CI/CD pipelines and during deployments
  • Reduced storage costs in registries and local caches
  • Lower network transfer costs
  • Faster horizontal scaling as new pods pull images

The locked tier achieves greater size reduction by removing optional nginx modules and debug symbols while retaining functionality.

Vulnerability Reduction

Fewer components dramatically reduces vulnerability exposure. Upstream nginx 1.26 inherits vulnerabilities from the Debian base image:

SeverityUpstream nginx:1.26.3ARMORRED (both tiers)
Critical180
High251
Medium570
Low110
Total1171

Vulnerability reduction: 99.1%


$ grype docker.io/library/nginx:1.26.3
[+] Vulnerability scan complete
[+] Critical: 18, High: 25, Medium: 57, Low: 11 (Total: 117)

$ grype ghcr.io/armorred/nginx:1.26-hardened
[+] Vulnerability scan complete
[+] Critical: 0, High: 1, Medium: 0, Low: 0 (Total: 1)

The single remaining HIGH vulnerability (OSV-2021-777 in libxml2) is a low-probability heap-use-after-free discovered through fuzzing that has no known exploit and no upstream fix. ARMORRED retains libxml2 because nginx uses it for XSLT transformations.

All critical and medium vulnerabilities exist in packages that nginx does not use (perl, libtiff, libexpat, libwebp, etc.) and are eliminated by not including them in the scratch-based build.

Shell Availability

Whether shell binaries are present in an ARMORRED image is an implementation detail that varies per image and build, not a defining property of the tier. Some images may include a shell in one tier but not the other; others may include or exclude it in both.

When a shell is absent, it eliminates entire classes of attacks:

  • Command injection vulnerabilities cannot execute shell commands
  • Container escape exploits that rely on sh or bash for post-exploitation fail
  • Attackers who achieve code execution cannot spawn an interactive shell
  • kubectl exec and podman exec cannot provide shell access

When a shell is present, it supports operational workflows, health check scripts, and debugging.

As an example, the current nginx 1.26 build:

Image/bin/shCoreutils
Upstream nginx:1.26.3PresentFull
ARMORRED HardenedAbsentAbsent
ARMORRED LockedPresentPresent

Always check the specific image's analysis page under Images for the exact shell availability of the image you are deploying.

When a shell is absent, use HTTP-based health checks rather than exec-based probes:


$ podman run --rm --entrypoint /bin/sh \
ghcr.io/armorred/nginx:1.26-hardened
Error: crun: executable file `/bin/sh` not found in $PATH: \
No such file or directory: OCI runtime attempted to invoke \
a command that was not found

Non-Root Execution

ARMORRED images run as non-root by default. The application binary executes as UID 65534 (nobody) with no elevated privileges.

Verify non-root execution:


$ podman exec nginx id
uid=65534 gid=0 groups=0

$ podman inspect ghcr.io/armorred/nginx:1.26-hardened | jq '.[0].Config.User'
"65534"

Running as non-root:

  • Prevents privilege escalation exploits from immediately gaining root
  • Limits the scope of filesystem access if the container is compromised
  • Aligns with the principle of least privilege
  • Satisfies pod security standards that prohibit root containers

Applications that require binding to privileged ports (<1024) must use Linux capabilities rather than running as root.

Read-Only Root Filesystem

ARMORRED images are designed to run with a read-only root filesystem. All writable state (logs, temporary files, application data) is directed to mounted volumes.

Example podman run with read-only root:


$ podman run -d \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=64M \
--tmpfs /var/log/nginx:rw \
-p 8080:8080 \
ghcr.io/armorred/nginx:1.26-hardened

Read-only root filesystems:

  • Prevent attackers from modifying binaries, libraries, or configuration after container start
  • Prevent persistence mechanisms such as cron jobs, SSH keys, or backdoors
  • Make containers immutable and cattle rather than pets
  • Align with the principle of immutable infrastructure

OCI Image Labels for Traceability

ARMORRED images include standardized OCI labels documenting the build:


$ podman inspect ghcr.io/armorred/nginx:1.26-hardened | jq '.[0].Config.Labels'
{
"org.armorred.base": "scratch",
"org.armorred.tier": "hardened",
"org.opencontainers.image.description": "Hardened Nginx 1.26.3 (hardened tier)",
"org.opencontainers.image.title": "nginx",
"org.opencontainers.image.version": "1.26.3"
}

These labels enable:

  • Automated detection of ARMORRED images in runtime environments
  • Policy enforcement based on org.armorred.tier value
  • Traceability back to the build process via org.opencontainers.image.description
  • Version identification for automated update workflows

Comparison Summary

Container build-time hardening provides foundational security through attack surface reduction. The following table uses nginx 1.26 as an example:

TechniqueBenefitHardenedLocked
FROM scratchNo base image vulnerabilitiesYesYes
Component minimization87% fewer packages19 packages20 packages
Size reduction38-63% smaller121 MB72 MB
Vulnerability reduction99% fewer CVEs1 CVE1 CVE
Non-root executionLimit privilegeUID 65534UID 65534
Read-only rootPrevent tamperingSupportedSupported

Specific details (shell availability, exact component lists, module inclusion) vary per image. Consult the Images section for per-image analysis.

These container-level protections work in combination with application build-time hardening and runtime protections to provide defense-in-depth.