Container images are the atomic unit of modern defense software delivery. Every workload running on Kubernetes defense clusters — mission applications, data pipelines, AI inference services — arrives as an immutable image that was built somewhere, from something. The security of everything downstream depends on the integrity of that build chain. In enterprise environments, a compromised container image is a serious incident. In a classified defense environment, it is a potential intelligence collection vector, a persistence mechanism for a nation-state actor, or the entry point for destructive capabilities targeting weapons-related infrastructure.

This article covers the full container image security lifecycle for defense: selecting and validating STIG-compliant base images, hardening builds with multi-stage Dockerfiles, running vulnerability scanning in air-gapped CI/CD pipelines, signing images with Cosign/Sigstore in disconnected environments, generating SBOMs for ATO packages, and enforcing registry integrity controls in Harbor. The audience is platform engineers and security architects working on classified or sensitive defense systems.

Why container image security matters more in defense than enterprise

In a commercial enterprise, a vulnerable or compromised container image represents a data breach risk, operational disruption, and regulatory liability. In a classified defense environment, the consequences extend to sources and methods exposure, mission compromise, and kinetic effects on systems that a software vulnerability may allow an adversary to control or disable. The threat model is qualitatively different: the adversary is not a financially motivated criminal group but a nation-state with persistent access operations and the patience to wait years for the right moment to activate a planted capability.

Container supply chain attacks exploit the layered nature of container images. A typical mission application image might be built FROM a base OS image, which installs runtime packages from a package repository, which in turn include transitive dependencies pulled at build time from upstream language package registries. The adversary does not need to compromise the defense contractor's internal systems — compromising a dependency three levels deep in that chain achieves the same effect. The SolarWinds and XZ Utils incidents demonstrated that this threat model is not theoretical; both were supply chain insertions that would have been technically undetectable without deep supply chain controls.

Defense environments add three requirements that make container image security significantly more operationally complex than in enterprise settings:

  • Air-gap requirements. Classified networks cannot pull images from internet registries at runtime. Every image dependency must be pre-approved, pre-scanned, and mirrored into an internal registry before it can be used — meaning the supply chain boundary is a hard perimeter that engineering teams fully own and are responsible for policing.
  • Formal authorization requirements. Software running on classified systems must complete an Authorization to Operate (ATO) process, which requires documented provenance for every software component. SBOM generation and image signing move from optional hygiene practices to mandatory ATO evidence deliverables.
  • Immutable infrastructure enforcement. Defense platforms increasingly mandate that no runtime modification of container images is permitted: containers are destroyed and replaced, never patched in place. This means the security posture of a deployed workload is permanently set at image build time, which makes build-time hardening and scanning gates non-negotiable rather than best-effort.

STIG-compliant base images

DISA (Defense Information Systems Agency) publishes Security Technical Implementation Guides (STIGs) for operating systems and platforms used in defense environments. For containerized workloads, the most operationally significant are the RHEL 8 STIG and RHEL 9 STIG, which apply to Red Hat Universal Base Image (UBI) derivatives — the most commonly used base images in defense container platforms, including DoD Platform One's Iron Bank hardened image repository.

The RHEL STIG enforces controls across several categories that differ materially from a default OS installation:

  • Kernel parameters (sysctl). IP forwarding disabled by default (net.ipv4.ip_forward=0), ICMP redirects rejected, TCP SYN cookies enabled, and randomized virtual address space (ASLR) enforced. These controls reduce the OS's exposure as a network relay and make memory exploitation harder.
  • PAM configuration. Password complexity rules (minimum length, character classes), account lockout after failed authentication attempts, and session recording requirements. For container service accounts that use key-based or token-based authentication, some of these controls may need documented exceptions.
  • auditd rules. The STIG specifies a comprehensive set of auditd rules covering filesystem access to sensitive paths, use of privilege escalation commands (sudo, su), modification of user and group databases, network configuration changes, and kernel module loading. In a container context, auditd typically runs on the host kernel and applies to all containers — the STIG rules should be applied at the node level, not per-container.
  • FIPS 140 cryptographic policy. The STIG requires the system-wide crypto policy to be set to FIPS or FIPS:OSCP, which disables TLS 1.0/1.1, MD5, SHA-1 for signatures, RC4, and DES/3DES. Application components that depend on deprecated algorithms will fail under the FIPS crypto policy — this is a common integration issue discovered late in hardening projects when the STIG base image is validated against application test suites.

The Iron Bank (Platform One's hardened container registry) publishes pre-hardened images that have been validated against DISA STIGs, scanned for vulnerabilities, and signed. For programs already on Platform One, building FROM an Iron Bank base image is the most practical approach: it provides a documented, pre-authorized baseline that satisfies the base image requirements of most ATOs. For programs not on Platform One, building and validating STIG compliance independently using OpenSCAP (oscap-docker or oscap against an exported container filesystem) is the equivalent process.

# Validate a container image filesystem against the RHEL 9 STIG
# Export the image filesystem to a temporary directory
docker export $(docker create registry.example.mil/myapp:v1.2.3) | tar -C /tmp/image-fs -xf -

# Run OpenSCAP against the exported filesystem
oscap xccdf eval \
  --profile xccdf_org.ssgproject.content_profile_stig \
  --results /tmp/stig-results.xml \
  --report /tmp/stig-report.html \
  --chroot /tmp/image-fs \
  /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml

Multi-stage build hardening

Multi-stage Docker builds are the most effective single technique for reducing container image attack surface. The principle is simple: use one or more intermediate build stages containing all the tools required to compile and package the application, and copy only the compiled artifacts into a final runtime stage that contains nothing the application does not need to run. A C++ application built in a stage containing gcc, make, cmake, and development headers produces a final image that contains only the binary and its runtime shared libraries.

# Stage 1: build — full toolchain, not present in final image
FROM registry.mil/ironbank/redhat/ubi/ubi9:latest AS builder
RUN dnf install -y gcc make cmake openssl-devel && dnf clean all
WORKDIR /build
COPY src/ .
RUN cmake -DCMAKE_BUILD_TYPE=Release . && make -j$(nproc)

# Stage 2: runtime — minimal base, no build tools
FROM registry.mil/ironbank/redhat/ubi/ubi9-micro:latest
COPY --from=builder /build/bin/myapp /usr/local/bin/myapp
# Non-root user — STIG and NSA hardening requirement
RUN useradd -r -u 10001 -s /sbin/nologin appuser
USER 10001
EXPOSE 8443
ENTRYPOINT ["/usr/local/bin/myapp"]

For statically compiled Go or Rust applications, the final stage can be scratch — a completely empty image containing only the binary. This eliminates the OS layer entirely, removing all OS-level vulnerabilities from the scanner's finding surface. The Go standard library's cross-compilation and static linking capabilities make scratch images practical for a broad class of defense microservices without additional build complexity.

When the application requires a shell or OS utilities at runtime (debugging in development, health check scripts, initialization logic), gcr.io/distroless images provide a middle ground: a minimal Debian or Alpine base containing only the language runtime and C library, with no shell, no package manager, and no system utilities. Distroless images are maintained by Google and published with vulnerability scan results; defense programs using distroless should mirror these images into their internal registry and maintain their own vulnerability assessment records.

Non-root user enforcement is a hardening requirement in both the NSA Kubernetes Hardening Guide and the STIG. The USER instruction in the Dockerfile sets the default user for all subsequent commands and for the container entrypoint. Use a numeric UID (not a named user) to avoid dependency on the /etc/passwd file, and choose a UID above 10000 to avoid conflicts with system user ranges. Admission controllers enforcing the Pod Security restricted profile will reject pods where runAsNonRoot is not true or where a container declares runAsUser: 0.

Vulnerability scanning in classified CI/CD pipelines

Vulnerability scanning is the quality gate that prevents images with known exploitable CVEs from reaching classified deployments. Two open-source scanners dominate defense container platform implementations: Trivy (Aqua Security) and Grype (Anchore). Both support offline operation — a non-negotiable requirement for CI/CD for air-gapped defense environments.

Trivy's offline mode requires the vulnerability database to be pre-downloaded and transferred to the air-gapped environment. The database covers OS packages (RPM, DEB, APK), language ecosystem packages (pip, npm, Maven, Go modules, Cargo), and application binary signatures. The transfer workflow using removable media or a cross-domain solution must be integrated into the team's regular operations as a scheduled task — a stale database (older than 14 days) is a common finding in classified environment security assessments.

# On connected (internet-facing) system — download Trivy DB
trivy image --download-db-only --cache-dir /transfer/trivy-cache

# Transfer /transfer/trivy-cache to air-gapped system via approved media

# On air-gapped CI/CD runner — scan image with local DB
trivy image \
  --skip-update \
  --cache-dir /opt/trivy-cache \
  --severity CRITICAL,HIGH \
  --exit-code 1 \
  --ignore-unfixed \
  --format json \
  --output /artifacts/scan-results.json \
  registry.classified.mil/myapp@sha256:abc123...

# Grype equivalent — disable auto-update, point to local DB
GRYPE_DB_AUTO_UPDATE=false \
GRYPE_DB_CACHE_DIR=/opt/grype-db \
grype registry.classified.mil/myapp@sha256:abc123... \
  --fail-on critical \
  --output json > /artifacts/grype-results.json

Scan policy gates define which findings block a pipeline build versus generate warnings. A defensible gate policy for classified environments:

  • Block (pipeline fails, image not pushed): any CRITICAL severity CVE, any HIGH severity CVE in a direct dependency with a CVSS exploitability sub-score above 7.0 or an EPSS score above 0.05.
  • Warn and require waiver: HIGH severity CVEs in transitive dependencies, HIGH severity CVEs where no vendor patch is available, MEDIUM severity CVEs.
  • Informational only: LOW and NEGLIGIBLE findings, CVEs affecting components that are demonstrably not reachable in the application's execution path.

Every waiver must be a signed, time-limited document linking the CVE identifier, the justification (no patch available, component not reachable, compensating control), the approving security officer, and an expiry date triggering re-assessment. Waivers stored as code-reviewed YAML files in the CI/CD repository provide an auditable record that satisfies ATO evidence requirements.

Image signing with Cosign/Sigstore in disconnected environments

Image signing provides cryptographic proof that a specific container image — identified by its SHA-256 content digest — was produced by an authorized pipeline and has not been modified since signing. Cosign (part of the Linux Foundation's Sigstore project) has become the standard image signing tool for container environments, producing OCI-compatible signatures stored as artifacts in the same registry as the image.

Sigstore's keyless signing flow uses OIDC identity tokens and public infrastructure (Fulcio CA and Rekor transparency log) to sign images without managing long-lived private keys. This model is well-suited to public open-source CI/CD but requires internet access to the Sigstore public infrastructure — incompatible with air-gapped classified environments. Disconnected environments have two practical options:

  • Keyed signing (recommended for classified). Generate a Cosign key pair; store the private key in an HSM or approved secrets manager (HashiCorp Vault with FIPS-validated backend, AWS CloudHSM, or Azure Dedicated HSM). The signing step in the CI/CD pipeline retrieves the key reference and signs the image digest. The public key is distributed to admission controllers for verification. No external infrastructure is required.
  • Private Sigstore instance. Deploy Fulcio and Rekor inside the classified network. This provides the keyless UX and transparency log benefits but requires operating additional infrastructure, including an internal OIDC identity provider. Appropriate for large programs with dedicated platform engineering teams.
# Generate Cosign key pair (run once, store private key in HSM/Vault)
cosign generate-key-pair --kms awskms:///arn:aws:kms:us-gov-east-1:123456789:key/abc-def

# Sign image after scanning gates pass — reference image by digest, not tag
IMAGE_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' myapp:v1.2.3)
cosign sign \
  --key awskms:///arn:aws:kms:us-gov-east-1:123456789:key/abc-def \
  --tlog-upload=false \
  registry.classified.mil/myapp@${IMAGE_DIGEST}

# Verify signature (used by admission controllers and in manual audits)
cosign verify \
  --key /etc/cosign/cosign.pub \
  --insecure-ignore-tlog=true \
  registry.classified.mil/myapp@sha256:abc123...

Admission controller enforcement of image signatures closes the gap between pipeline signing and runtime deployment. A Kyverno ClusterPolicy with a verifyImages rule denies any pod that references an image without a valid signature from the approved Cosign public key. The policy should also enable mutateDigest: true, which rewrites mutable tag references to immutable digest references in the pod spec at admission time — ensuring the container runtime pulls exactly the image that was verified, not a subsequent push to the same tag.

SBOM generation for ATO packages

A Software Bill of Materials (SBOM) is a machine-readable inventory of every software component in a deployed artifact — OS packages, language runtime libraries, application dependencies, and their relationships. For ATO packages, the SBOM provides the authorizing official with supply chain visibility into the deployed system and is increasingly a required deliverable under DoD software acquisition policy and CISA guidance implementing Executive Order 14028.

Syft (Anchore) is the standard open-source SBOM generator for container images. It scans the image filesystem layer by layer, identifies packages by both OS package database records and language ecosystem lock files, and outputs structured SBOM documents. Running Syft against the final image digest (not the Dockerfile or source repository) captures the actual deployed component set, including packages installed transitively or added during the build process that are not explicitly listed in application dependency files.

# Generate SBOM in both SPDX and CycloneDX formats from final image
syft registry.classified.mil/myapp@sha256:abc123... \
  -o spdx-json=/artifacts/sbom.spdx.json \
  -o cyclonedx-json=/artifacts/sbom.cdx.json

# Attach SBOM to the image in the registry (stored as OCI artifact)
cosign attach sbom \
  --sbom /artifacts/sbom.spdx.json \
  --type spdx \
  registry.classified.mil/myapp@sha256:abc123...

# Verify SBOM attachment
cosign verify-attestation \
  --key /etc/cosign/cosign.pub \
  --insecure-ignore-tlog=true \
  --type spdx \
  registry.classified.mil/myapp@sha256:abc123...

On the question of SPDX versus CycloneDX: both formats are accepted by CISA guidance and are capable of representing the same component information. SPDX (ISO/IEC 5962:2021) is the older standard with the stronger mandate in government contexts; CycloneDX has stronger tooling support for vulnerability enrichment via VEX (Vulnerability Exploitability eXchange) documents. For SBOM enforcement in defense pipelines, generating both formats from a single Syft invocation costs nothing and ensures compatibility with any downstream ATO tooling or authorizing official preference.

The SBOM submitted to the AO should be accompanied by a vulnerability disclosure document mapping SBOM component identifiers to scan findings and their disposition (patched, waived with justification, not affected). This combined package — image digest, SBOM, scan results, waivers, and Cosign signature — forms the supply chain evidence set that auditors use to assess the trustworthiness of a deployed system.

Container image registry security

Harbor is the CNCF-graduated container registry most widely deployed in defense and classified environments. Its feature set addresses the specific operational requirements of defense registries: tag immutability, content trust integration with Cosign, role-based access control, vulnerability scanning integration (Trivy), and replication policies for multi-enclave registry networks. Running Harbor in a classified environment requires attention to several configuration areas that default settings do not enforce.

Tag immutability prevents any push operation from overwriting an existing image tag. Without this control, a compromised CI/CD service account or a misconfigured pipeline could silently replace an approved, signed image with a malicious or unsigned one under the same tag. Harbor's tag immutability rules are configured per-project and can be scoped to specific tag patterns — for example, locking all tags matching v[0-9]* (release versions) while allowing mutable dev-* tags in development projects.

Content trust policy in Harbor integrates with Cosign to enforce signature verification at pull time. When content trust is enabled for a project, Harbor's proxy layer calls Cosign verify for every image pull request and returns an authorization error if the image lacks a valid signature from the configured public key. This provides registry-level enforcement independent of the Kubernetes admission controller — images cannot be pulled even by tooling that bypasses the admission webhook.

# Harbor project configuration via CLI (harbor-cli or curl against Harbor API)
# Enable tag immutability for production project
curl -X POST https://registry.classified.mil/api/v2.0/projects/mission-apps/immutabletagrules \
  -H "Content-Type: application/json" \
  -u "admin:${HARBOR_ADMIN_PASSWORD}" \
  -d '{
    "action": "immutableTagRule",
    "scope_selectors": {"repository": [{"decoration": "repoMatches","pattern": "**"}]},
    "tag_selectors": [{"decoration": "matches","pattern": "v[0-9]*"}]
  }'

# Enable vulnerability scanning on push for all projects
curl -X PUT https://registry.classified.mil/api/v2.0/projects/mission-apps \
  -H "Content-Type: application/json" \
  -u "admin:${HARBOR_ADMIN_PASSWORD}" \
  -d '{"metadata": {"auto_scan": "true", "severity": "critical"}}'

Pull-through cache in Harbor serves a critical function in multi-enclave architectures. The connected (lower-classification) Harbor instance is configured with upstream proxy cache projects pointing to approved external registries (Red Hat Registry, Iron Bank). On the classified side, a separate Harbor instance has no upstream registry configured — it serves only images that were pulled through the cache on the connected side, scanned, signed, and physically transferred to the classified-side registry via the approved cross-domain transfer mechanism. This creates a controlled image promotion workflow where every image must pass security controls before entering the classified environment rather than at entry.

Garbage collection policy in Harbor removes untagged manifests and unused layers on a defined schedule. In classified environments with limited storage capacity, garbage collection prevents registry storage from growing unboundedly as images are replaced by new versions. The recommended configuration runs garbage collection weekly during a maintenance window, retains a configurable number of historical tagged images per repository for rollback capability, and generates a deletion log for auditability. Image deletion in a classified registry must be coordinated with the ATO evidence retention requirements — SBOM and scan artifacts for deployed image versions may need to be retained beyond the image lifecycle.

Key insight: Container image security is not a single control but a defense-in-depth chain: STIG base images reduce the inherited vulnerability surface; multi-stage builds eliminate unused attack surface from the runtime image; vulnerability scanning at build time catches known CVEs before deployment; image signing provides integrity verification from build to runtime; SBOM generation provides supply chain transparency for authorization; and registry controls (immutability, content trust, pull-through cache) enforce the chain at the distribution layer. A gap in any link in that chain — an unsigned image, a stale vulnerability database, a mutable tag — is the attack surface that an adversary with supply chain access will exploit.