Stage: base (DHI dev variant - has shell + npm for builds)
Migrate to Docker Hardened Imagesโ
The pro-active approach: Start Secure. DHI images are purpose-built from the ground up to be extremely minimal - not stripped-down versions of something bloated.
In this section we'll convert catalog-service-node from node:25-slim (where the best-practices track ended) to a Docker Hardened Image, then compare the results.
Before you startโ
Make sure you have:
- Completed the Setup steps
- Decided on free vs paid DHI tier (and know your
<DHI_PREFIX>) - Logged in to
dhi.ioif using the free tier (docker login dhi.io)
Update the Dockerfileโ
Open catalog-service-node/Dockerfile and replace it with this DHI-based version:
###########################################################
###########################################################
FROM <DHI_PREFIX>node:24-debian13-dev AS base
WORKDIR /usr/local/app
COPY package.json package-lock.json ./
###########################################################
# Stage: dev
###########################################################
FROM base AS dev
ENV NODE_ENV=development
RUN npm install
CMD ["yarn", "dev-container"]
###########################################################
# Stage: production-dependencies
###########################################################
FROM base AS production-dependencies
ENV NODE_ENV=production
RUN npm ci --production --ignore-scripts && npm cache clean --force
###########################################################
# Stage: final (DHI runtime - distroless, no shell)
###########################################################
FROM <DHI_PREFIX>node:24-debian13 AS final
ENV NODE_ENV=production
COPY /usr/local/app/node_modules ./node_modules
COPY ./src ./src
EXPOSE 3000
CMD ["node", "src/index.js"]
The two critical changes:
- FROM node:25-slim AS base
+ FROM <DHI_PREFIX>node:24-debian13-dev AS base # build stage
+ FROM <DHI_PREFIX>node:24-debian13 AS final # runtime - distroless
Notice the structure: the dev variant is used as the build environment (it has npm), but the final image is built FROM the distroless runtime variant. Only the compiled node_modules and src are copied in.
Build the DHI versionโ
docker build -t catalog-service:dhi --sbom=true --provenance=mode=max .
Compare all three imagesโ
docker images catalog-service
IMAGE ID DISK USAGE CONTENT SIZE
catalog-service:dhi ac3a0d465de4 191MB 40.3MB
catalog-service:latest 48806e62b871 1.62GB 413MB
catalog-service:slim 8d03cef7a79f 368MB 84.1MB
The DHI runtime is 10ร smaller than the original and half the size of the slim version - and we still haven't looked at security.
Scout quickview - all 7 policies greenโ
docker scout quickview catalog-service:dhi --org <YOUR_ORG>
Target โ catalog-service:dhi โ 0C 0H 0M 0L
Policy status SUCCEEDED (7/7 policies met)
Status โ Policy โ Results
โโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโ
โ โ Default non-root user โ
โ โ No AGPL v3 licenses โ
โ โ No fixable critical or high vulnerabilities โ
โ โ No high-profile vulnerabilities โ
โ โ No unapproved base images โ
โ โ Supply chain attestations โ
โ โ No outdated base images โ
7/7 policies met. Up from 4/7 at the start of the workshop.
Full before/after comparisonโ
docker scout compare \
--ignore-unchanged \
--to catalog-service \
catalog-service:dhi \
--org <YOUR_ORG>
vulnerabilities โ 0C 0H 0M 0L โ 2C 26H 25M 122L 4?
size โ 40 MB (-358 MB) โ 398 MB
packages โ 211 (-595) โ 806
The numbers:
- โ 595 packages removed - 595 fewer potential CVE vectors
- โ 179 vulnerabilities removed across all severities
- โ Image is 10ร smaller
The no-shell demoโ
Because the DHI runtime is distroless, an attacker who gains code execution cannot drop to a shell:
docker run --rm catalog-service:dhi sh
Expected: error - sh does not exist in the image.
Compare to slim:
docker run --rm catalog-service:slim sh -c "echo 'shell available - attack surface'"
The slim image still gives the attacker a shell. The DHI image does not. That's a meaningful difference in what an exploit can do once it lands.
DHI vs slim - property comparisonโ
| Property | node:25-slim | DHI runtime |
|---|---|---|
| CVEs (critical/high) | 0C 2H | 0C 0H |
| Package count | ~272 | ~12 |
| Shell in runtime | Yes (sh) | No (distroless) |
| Non-root by default | Manual | Built-in |
| SBOM | Build-time only | Cryptographically signed |
| VEX document | No | Yes |
| SLSA provenance | Build-time only | Verified |
| FIPS variant | No | Yes (-fips tag) |
| STIG variant | No | Yes |
| 7-day CVE SLA | No | Yes |
Continue to Attestations & Scanner Integration.