From 1270e1ae47fdb31e450ef68e78c364621d864bd7 Mon Sep 17 00:00:00 2001 From: Ajeet Raina Date: Fri, 13 Mar 2026 15:17:50 +0530 Subject: [PATCH 01/12] user guide: backstage --- content/guides/dhi-backstage.md | 456 ++++++++++++++++++++++++++++++++ 1 file changed, 456 insertions(+) create mode 100644 content/guides/dhi-backstage.md diff --git a/content/guides/dhi-backstage.md b/content/guides/dhi-backstage.md new file mode 100644 index 00000000000..6aa2fd31933 --- /dev/null +++ b/content/guides/dhi-backstage.md @@ -0,0 +1,456 @@ +--- +title: Secure a Backstage application with Docker Hardened Images +description: Secure a Backstage developer portal container using Docker Hardened Images, covering native module compilation, multi-stage builds, Socket Firewall protection, and distroless runtime images. +summary: Learn how to secure a Backstage developer portal using Docker Hardened Images (DHI), handle native module compilation with better-sqlite3, add Socket Firewall protection during dependency installation, and produce a distroless runtime image using DHI Enterprise customizations. +keywords: docker hardened images, dhi, backstage, CNCF, developer portal, node.js, native modules, sqlite, better-sqlite3, distroless, socket firewall, dhictl, multi-stage build +tags: ["Docker Hardened Images", "dhi"] +params: + proficiencyLevel: Intermediate + time: 45 minutes + prerequisites: + - Docker Desktop or Docker Engine with BuildKit enabled + - A Docker Hub account authenticated with docker login and docker login dhi.io + - A Backstage project created with @backstage/create-app + - Basic familiarity with multi-stage Dockerfiles and Node.js native modules +--- + +This guide shows how to secure a Backstage application using Docker Hardened Images (DHI). Backstage is a CNCF open source developer portal used by thousands of organizations to manage their software catalogs, templates, and developer tooling. + +By the end of this guide, you'll have a Backstage container image that is distroless, runs as a non-root user by default, and has dramatically fewer CVEs than the standard `node:24-trixie-slim` base image while still supporting the native module compilation that Backstage requires. + +## Prerequisites + +- Docker Desktop or Docker Engine with BuildKit enabled +- A Docker Hub account authenticated with `docker login` and `docker login dhi.io` +- A Backstage project created with `@backstage/create-app` + +## Why Backstage needs customization + +The DHI migration examples cover applications where you can swap the base image and everything works. Backstage is different. It uses `better-sqlite3` and other packages that compile native Node.js modules at install time, which means the build stage needs `g++`, `make`, `python3`, and `sqlite-dev` — none of which are in the base `dhi.io/node` image. The runtime image only needs the shared library (`sqlite-libs`) that the compiled native module links against. + +This is a common pattern. Any Node.js application that depends on native addons (such as `bcrypt`, `sharp`, `sqlite3`, or `node-canvas`) faces the same challenge. The approach in this guide applies to all of them. + +## Step 1: Examine the original Dockerfile + +The official Backstage documentation recommends a multi-stage Dockerfile using `node:24-trixie-slim` (Debian). A typical setup looks like this: + +```dockerfile +# Stage 1 - Create yarn install skeleton layer +FROM node:24-trixie-slim AS packages +WORKDIR /app +COPY backstage.json package.json yarn.lock ./ +COPY .yarn ./.yarn +COPY .yarnrc.yml ./ +COPY packages packages +COPY plugins plugins +RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 \ + -exec rm -rf {} \+ + +# Stage 2 - Install dependencies and build packages +FROM node:24-trixie-slim AS build +ENV PYTHON=/usr/bin/python3 +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ + apt-get install -y --no-install-recommends python3 g++ build-essential && \ + rm -rf /var/lib/apt/lists/* +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ + apt-get install -y --no-install-recommends libsqlite3-dev && \ + rm -rf /var/lib/apt/lists/* +USER node +WORKDIR /app +COPY --from=packages --chown=node:node /app . +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn install --immutable +COPY --chown=node:node . . +RUN yarn tsc +RUN yarn --cwd packages/backend build +RUN mkdir packages/backend/dist/skeleton packages/backend/dist/bundle \ + && tar xzf packages/backend/dist/skeleton.tar.gz \ + -C packages/backend/dist/skeleton \ + && tar xzf packages/backend/dist/bundle.tar.gz \ + -C packages/backend/dist/bundle + +# Stage 3 - Build the actual backend image +FROM node:24-trixie-slim +ENV PYTHON=/usr/bin/python3 +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ + apt-get install -y --no-install-recommends python3 g++ build-essential && \ + rm -rf /var/lib/apt/lists/* +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ + apt-get install -y --no-install-recommends libsqlite3-dev && \ + rm -rf /var/lib/apt/lists/* +USER node +WORKDIR /app +COPY --from=build --chown=node:node /app/.yarn ./.yarn +COPY --from=build --chown=node:node /app/.yarnrc.yml ./ +COPY --from=build --chown=node:node /app/backstage.json ./ +COPY --from=build --chown=node:node /app/yarn.lock \ + /app/package.json \ + /app/packages/backend/dist/skeleton/ ./ +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn workspaces focus --all --production +COPY --from=build --chown=node:node /app/packages/backend/dist/bundle/ ./ +CMD ["node", "packages/backend", "--config", "app-config.yaml"] +``` + +Run this image and inspect what's available inside the container: + +```console +docker build -t backstage:init . +docker run -d \ + -e APP_CONFIG_backend_database_client='better-sqlite3' \ + -e APP_CONFIG_backend_database_connection=':memory:' \ + -e APP_CONFIG_auth_providers_guest_dangerouslyAllowOutsideDevelopment='true' \ + -p 7007:7007 \ + -u 1000 \ + --cap-drop=ALL \ + --read-only \ + --tmpfs /tmp \ + backstage:init +``` + +This works, but the runtime container has a shell, a package manager, and yarn. None of these are needed to run Backstage. Run `docker exec` to see what's accessible inside: + +```console +docker exec -it sh +$ cat /etc/shells +# /etc/shells: valid login shells +/bin/sh +/usr/bin/sh +/bin/bash +/usr/bin/bash +/bin/rbash +/usr/bin/rbash +/usr/bin/dash +$ yarn --version +4.12.0 +$ dpkg --version +dpkg version 1.22.11 (arm64). +$ whoami +node +$ id +uid=1000(node) gid=1000(node) groups=1000(node) +``` + +The `node:24-trixie-slim` image ships with three shells (`dash`, `bash`, and `rbash`), a package manager (`dpkg`), and `yarn`. Each of these tools increases the attack surface. An attacker who gains access to this container could use them for lateral movement across your infrastructure. + +## Step 2: Switch the build stages to DHI + +Replace all three stages with DHI equivalents. DHI Node.js images use Alpine, so the package installation commands change from `apt-get` to `apk`: + +```dockerfile +# Stage 1: prepare packages +FROM --platform=$BUILDPLATFORM dhi.io/node:24-alpine3.23-dev AS packages +WORKDIR /app +COPY backstage.json package.json yarn.lock ./ +COPY .yarn ./.yarn +COPY .yarnrc.yml ./ +COPY packages packages +COPY plugins plugins +RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 \ + -exec rm -rf {} \+ + +# Stage 2: build the application +FROM --platform=$BUILDPLATFORM dhi.io/node:24-alpine3.23-dev AS build +ENV PYTHON=/usr/bin/python3 +RUN apk add --no-cache g++ make python3 sqlite-dev && \ + rm -rf /var/lib/apk/lists/* +WORKDIR /app +COPY --from=packages --chown=node:node /app . +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn install --immutable +COPY --chown=node:node . . +RUN yarn tsc +RUN yarn --cwd packages/backend build +RUN mkdir packages/backend/dist/skeleton packages/backend/dist/bundle \ + && tar xzf packages/backend/dist/skeleton.tar.gz \ + -C packages/backend/dist/skeleton \ + && tar xzf packages/backend/dist/bundle.tar.gz \ + -C packages/backend/dist/bundle + +# Final Stage: create the runtime image +FROM dhi.io/node:24-alpine3.23-dev +ENV PYTHON=/usr/bin/python3 +RUN apk add --no-cache g++ make python3 sqlite-dev && \ + rm -rf /var/lib/apk/lists/* +WORKDIR /app +COPY --from=build --chown=node:node /app/.yarn ./.yarn +COPY --from=build --chown=node:node /app/.yarnrc.yml ./ +COPY --from=build --chown=node:node /app/backstage.json ./ +COPY --from=build --chown=node:node /app/yarn.lock \ + /app/package.json \ + /app/packages/backend/dist/skeleton/ ./ +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn workspaces focus --all --production \ + && rm -rf "$(yarn cache clean)" +COPY --from=build --chown=node:node /app/packages/backend/dist/bundle/ ./ +CMD ["node", "packages/backend", "--config", "app-config.yaml"] +``` + +Build and tag this version: + +```console +docker build -t backstage:dhi-dev . +``` + +> **Note** +> +> The `-dev` variant includes a shell and package manager, which is why `apk add` works. Backstage requires `python3` and native build tools in the runtime image because `yarn workspaces focus --all --production` recompiles native modules during the production install. This is specific to Backstage's build process — most Node.js applications can use the standard (non-dev) DHI runtime variant without additional packages. + +The DHI images come with attestations that the original `node:24-trixie-slim` images don't have. Check what's attached: + +```console +docker scout attest list dhi.io/node:24-alpine3.23 +``` + +DHI images ship with 15 attestations including CycloneDX SBOM, SLSA provenance, OpenVEX, Scout health reports, secret scans, virus/malware reports, and an SLSA verification summary. + +## Step 3: Add Socket Firewall protection + +DHI provides `-sfw` (Socket Firewall) variants for Node.js images. Socket Firewall intercepts `npm` and `yarn` commands during the build to detect and block malicious packages before they execute install scripts. + +To enable Socket Firewall, change the `-dev` tags to `-sfw-dev` in all three stages. The SFW version of the Dockerfile: + +```dockerfile +# Stage 1: prepare packages +FROM --platform=$BUILDPLATFORM dhi.io/node:24-alpine3.23-sfw-dev AS packages +WORKDIR /app +COPY backstage.json package.json yarn.lock ./ +COPY .yarn ./.yarn +COPY .yarnrc.yml ./ +COPY packages packages +COPY plugins plugins +RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 \ + -exec rm -rf {} \+ + +# Stage 2: build the packages +FROM --platform=$BUILDPLATFORM dhi.io/node:24-alpine3.23-sfw-dev AS build-packages +ENV PYTHON=/usr/bin/python3 +RUN apk add --no-cache g++ make python3 sqlite-dev && \ + rm -rf /var/lib/apk/lists/* +WORKDIR /app +COPY --from=packages --chown=node:node /app . +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn install --immutable +COPY --chown=node:node . . +RUN yarn tsc +RUN yarn --cwd packages/backend build +RUN mkdir packages/backend/dist/skeleton packages/backend/dist/bundle \ + && tar xzf packages/backend/dist/skeleton.tar.gz \ + -C packages/backend/dist/skeleton \ + && tar xzf packages/backend/dist/bundle.tar.gz \ + -C packages/backend/dist/bundle + +# Final Stage: create the runtime image +FROM dhi.io/node:24-alpine3.23-sfw-dev +ENV PYTHON=/usr/bin/python3 +RUN apk add --no-cache g++ make python3 sqlite-dev && \ + rm -rf /var/lib/apk/lists/* +WORKDIR /app +COPY --from=build-packages --chown=node:node /app/.yarn ./.yarn +COPY --from=build-packages --chown=node:node /app/.yarnrc.yml ./ +COPY --from=build-packages --chown=node:node /app/backstage.json ./ +COPY --from=build-packages --chown=node:node /app/yarn.lock \ + /app/package.json \ + /app/packages/backend/dist/skeleton/ ./ +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn workspaces focus --all --production \ + && rm -rf "$(yarn cache clean)" +COPY --from=build-packages --chown=node:node /app/packages/backend/dist/bundle/ ./ +CMD ["node", "packages/backend", "--config", "app-config.yaml"] +``` + +Build this version: + +```console +docker build -t backstage:dhi-sfw-dev . +``` + +When you build, you'll see Socket Firewall messages in the build output: `Protected by Socket Firewall` for any `yarn` and `npm` commands executed in the Dockerfile or in the running containers. + +> **Tip** +> +> The `-sfw-dev` variant is larger (1.9 GB versus 1.72 GB) because Socket Firewall adds monitoring tooling. The security benefit during `yarn install` outweighs the size increase. + +## Step 4: Remove the shell and the package manager with DHI Enterprise customizations + +The previous steps still use the `-dev` or `-sfw-dev` variant as the runtime image, which includes a shell and package manager. DHI Enterprise customizations let you start from the base (non-dev) image — which has no shell and no package manager — and add only the runtime libraries and language runtimes your application needs. + +> **Important** +> +> Only add runtime libraries to the customization, not build tools or language runtimes as system packages. Compile everything in the build stage (which uses the `-dev` variant), copy the pre-built `node_modules` into the runtime image, and use an OCI artifact for any language runtimes needed at runtime. + +For Backstage, the runtime image needs: + +- **sqlite-libs** — the shared library that the compiled `better-sqlite3` native module links against (added as a system package). +- **Python** — if your Backstage plugins or configuration require Python at runtime. Added as the `python-3.14` system package, which installs Python from the hardened DHI package feed. + +Docker will continuously build with SLSA Level 3 compliance and patch these customized images within the guaranteed SLA for CVE patching. + +### Using the Docker Hub UI + +After you mirror the Node.js DHI repository to your organization's namespace: + +1. Open the mirrored Node.js repository in Docker Hub. +2. Select **Customize** and choose the `node:24-alpine3.23` tag. +3. Under **Packages**, add `sqlite-libs` and `python-3.14`. +4. Create the customization. + +For more information, see [Customize an image](/dhi/how-to/customize/). + +### Using the `dhictl` CLI + +`dhictl` is Docker's command-line tool for managing Docker Hardened Images. It lets you browse the DHI catalog, mirror images, and create customizations directly from your terminal — making it easy to integrate DHI into CI/CD pipelines and infrastructure-as-code workflows. You can install `dhictl` as a standalone binary or as a Docker CLI plugin (`docker dhi`); it will also be available by default in Docker Desktop soon. + +Rather than writing the customization YAML by hand, use `dhictl` to scaffold a starting point: + +```console +dhictl customization prepare --org YOUR_ORG node 24-alpine3.23 \ + --destination YOUR_ORG/dhi-node \ + --name "backstage" \ + --tag-suffix "_backstage" \ + --output node-backstage.yaml +``` + +Edit the generated file to add the runtime libraries: + +```yaml +name: backstage + +source: dhi/node +tag_definition_id: node/alpine-3.23/24 + +destination: YOUR_ORG/dhi-node +tag_suffix: _backstage + +platforms: + - linux/amd64 + - linux/arm64 + +contents: + packages: + - sqlite-libs + - python-3.14 + +accounts: + root: true + runs-as: node + users: + - name: node + uid: 1000 + groups: + - name: node + gid: 1000 + +``` + +Then create the customization: + +```console +dhictl customization create --org YOUR_ORG node-backstage.yaml +``` + +Monitor the build progress: + +```console +dhictl customization build list --org YOUR_ORG YOUR_ORG/dhi-node "backstage" +``` + +Docker builds the customized image on its secure infrastructure and publishes it as `YOUR_ORG/dhi-node:24-alpine3.23_backstage`. + +> **Note** +> +> If your Backstage configuration does not require Python at runtime, you can omit the `python-3.14` from the packages list. The `sqlite-libs` package alone is sufficient to run Backstage with `better-sqlite3`. + +### Updated Dockerfile + +Update only the final stage of your Dockerfile to use the customized image: + +```dockerfile +# Final Stage: create the runtime image +FROM YOUR_ORG/dhi-node:24-alpine3.23_backstage +WORKDIR /app +COPY --from=build --chown=node:node /app/node_modules ./node_modules +COPY --from=build --chown=node:node /app/packages/backend/dist/bundle/ ./ +CMD ["node", "packages/backend", "--config", "app-config.yaml"] +``` + +Since the customization includes only runtime libraries and OCI artifacts — no build tools, no package manager, no shell — the resulting image is distroless: + +```console +docker run --rm YOUR_ORG/dhi-node:24-alpine3.23_backstage sh -c "echo hello" +docker: Error response from daemon: ... exec: "sh": executable file not found in $PATH +``` + +With the Enterprise customization: + +- The runtime image is distroless — no shell, no package manager. +- Docker automatically rebuilds your customized image when the base Node.js image or any of its packages receive a security patch. +- The full chain of trust is maintained, including SLSA Build Level 3 provenance. +- Both the Node.js and Python runtimes are tracked in the image SBOM. + +Confirm the container no longer has shell access: + +```console +docker exec -it sh +OCI runtime exec failed: exec failed: unable to start container process: ... +``` + +Use [Docker Debug](/dhi/how-to/debug/) if you need to troubleshoot a running distroless container. + +> **Note** +> +> If your organization requires FIPS/STIG compliant images, that's also an option in DHI Enterprise. + +## Step 5: Verify the results + +Compare the DHI-based image against the original using Docker Scout: + +```console +docker scout compare backstage:dhi \ + --to backstage:init \ + --platform linux/amd64 \ + --ignore-unchanged +``` + +A typical comparison across the approaches shows results similar to the following: + +| Metric | Original | DHI -dev | DHI -sfw-dev | Enterprise | +|--------|----------|----------|--------------|------------| +| Disk usage | 1.61 GB | 1.72 GB | 1.9 GB | 1.49 GB | +| Content size | 268 MB | 288 MB | 328 MB | 247 MB | +| Shell in runtime | Yes | Yes | Yes | No | +| Package manager | Yes | Yes | Yes | No | +| Non-root default | No | No | No | Yes | +| Socket Firewall | No | No | Yes (build) | Yes (build) / No (runtime) | +| SLSA provenance | No | Base only | Base only | Full (Level 3) | + +> **Note** +> +> The `-sfw-dev` variant is larger because Socket Firewall adds monitoring tooling to the image. This is expected — the additional size is in the build stages, and the security benefit during `yarn install` outweighs the size increase. + +For a more thorough assessment, scan with multiple tools: + +```console +trivy image backstage:dhi +grype backstage:dhi +docker scout quickview backstage:dhi +``` + +Different scanners detect different issues. Running all three gives you the most complete view of your security posture. + +## What's next + +- [Customize an image](/dhi/how-to/customize/) — complete reference on the Enterprise customization UI. +- [Create and build a DHI](/dhi/how-to/build/) — learn how to write a DHI definition file, build images locally. +- [Use the DHI CLI](/dhi/how-to/cli/) — manage DHI images, mirrors, and customizations from the command line. +- [Migrate to DHI](/dhi/migration/) — for applications that work with standard DHI images without additional packages. +- [Compare images](/dhi/how-to/compare/) — evaluate security improvements between your original and hardened images. +- [Docker Debug](/dhi/how-to/debug/) — troubleshoot distroless containers that have no shell. From 544a1549a31879c2bff121072e43283427539e04 Mon Sep 17 00:00:00 2001 From: "Ajeet Singh Raina, Docker Captain, ARM Innovator" Date: Fri, 13 Mar 2026 16:10:58 +0530 Subject: [PATCH 02/12] Update dhi-backstage.md --- content/guides/dhi-backstage.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/content/guides/dhi-backstage.md b/content/guides/dhi-backstage.md index 6aa2fd31933..029cdccfc8f 100644 --- a/content/guides/dhi-backstage.md +++ b/content/guides/dhi-backstage.md @@ -200,7 +200,7 @@ Build and tag this version: docker build -t backstage:dhi-dev . ``` -> **Note** +> [!NOTE] > > The `-dev` variant includes a shell and package manager, which is why `apk add` works. Backstage requires `python3` and native build tools in the runtime image because `yarn workspaces focus --all --production` recompiles native modules during the production install. This is specific to Backstage's build process — most Node.js applications can use the standard (non-dev) DHI runtime variant without additional packages. @@ -275,7 +275,7 @@ docker build -t backstage:dhi-sfw-dev . When you build, you'll see Socket Firewall messages in the build output: `Protected by Socket Firewall` for any `yarn` and `npm` commands executed in the Dockerfile or in the running containers. -> **Tip** +> [!TIP] > > The `-sfw-dev` variant is larger (1.9 GB versus 1.72 GB) because Socket Firewall adds monitoring tooling. The security benefit during `yarn install` outweighs the size increase. @@ -365,7 +365,7 @@ dhictl customization build list --org YOUR_ORG YOUR_ORG/dhi-node "backstage" Docker builds the customized image on its secure infrastructure and publishes it as `YOUR_ORG/dhi-node:24-alpine3.23_backstage`. -> **Note** +> [!NOTE] > > If your Backstage configuration does not require Python at runtime, you can omit the `python-3.14` from the packages list. The `sqlite-libs` package alone is sufficient to run Backstage with `better-sqlite3`. @@ -405,7 +405,7 @@ OCI runtime exec failed: exec failed: unable to start container process: ... Use [Docker Debug](/dhi/how-to/debug/) if you need to troubleshoot a running distroless container. -> **Note** +> [!NOTE] > > If your organization requires FIPS/STIG compliant images, that's also an option in DHI Enterprise. @@ -432,7 +432,7 @@ A typical comparison across the approaches shows results similar to the followin | Socket Firewall | No | No | Yes (build) | Yes (build) / No (runtime) | | SLSA provenance | No | Base only | Base only | Full (Level 3) | -> **Note** +> [!NOTE] > > The `-sfw-dev` variant is larger because Socket Firewall adds monitoring tooling to the image. This is expected — the additional size is in the build stages, and the security benefit during `yarn install` outweighs the size increase. From 547c8771889ca2dc69db4f1ed912fe1739b61b48 Mon Sep 17 00:00:00 2001 From: "Ajeet Singh Raina, Docker Captain, ARM Innovator" Date: Fri, 13 Mar 2026 16:14:54 +0530 Subject: [PATCH 03/12] Update dhi-backstage.md --- content/guides/dhi-backstage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guides/dhi-backstage.md b/content/guides/dhi-backstage.md index 029cdccfc8f..3433071628b 100644 --- a/content/guides/dhi-backstage.md +++ b/content/guides/dhi-backstage.md @@ -1,6 +1,6 @@ --- title: Secure a Backstage application with Docker Hardened Images -description: Secure a Backstage developer portal container using Docker Hardened Images, covering native module compilation, multi-stage builds, Socket Firewall protection, and distroless runtime images. +description: Secure a Backstage developer portal using Docker Hardened Images, covering native module compilation, Socket Firewall protection, and distroless runtime images. summary: Learn how to secure a Backstage developer portal using Docker Hardened Images (DHI), handle native module compilation with better-sqlite3, add Socket Firewall protection during dependency installation, and produce a distroless runtime image using DHI Enterprise customizations. keywords: docker hardened images, dhi, backstage, CNCF, developer portal, node.js, native modules, sqlite, better-sqlite3, distroless, socket firewall, dhictl, multi-stage build tags: ["Docker Hardened Images", "dhi"] From ad7dac1f89ae9ab7961c2bbba14618f71a17dba0 Mon Sep 17 00:00:00 2001 From: "Ajeet Singh Raina, Docker Captain, ARM Innovator" Date: Fri, 13 Mar 2026 16:16:41 +0530 Subject: [PATCH 04/12] Clarify `dhictl` CLI description Reworded the description of the `dhictl` CLI for clarity. --- content/guides/dhi-backstage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guides/dhi-backstage.md b/content/guides/dhi-backstage.md index 3433071628b..67505cb5440 100644 --- a/content/guides/dhi-backstage.md +++ b/content/guides/dhi-backstage.md @@ -307,7 +307,7 @@ For more information, see [Customize an image](/dhi/how-to/customize/). ### Using the `dhictl` CLI -`dhictl` is Docker's command-line tool for managing Docker Hardened Images. It lets you browse the DHI catalog, mirror images, and create customizations directly from your terminal — making it easy to integrate DHI into CI/CD pipelines and infrastructure-as-code workflows. You can install `dhictl` as a standalone binary or as a Docker CLI plugin (`docker dhi`); it will also be available by default in Docker Desktop soon. +`dhictl` is Docker's command-line tool for managing Docker Hardened Images. It lets you browse the DHI catalog, mirror images, and create customizations directly from your terminal for integrating DHI into CI/CD pipelines and infrastructure-as-code workflows. You can install `dhictl` as a standalone binary or as a Docker CLI plugin (`docker dhi`); it will also be available by default in Docker Desktop soon. Rather than writing the customization YAML by hand, use `dhictl` to scaffold a starting point: From 56671b104be1e2cb8a730f45833bd8fa2633d8da Mon Sep 17 00:00:00 2001 From: "Ajeet Singh Raina, Docker Captain, ARM Innovator" Date: Fri, 13 Mar 2026 16:28:57 +0530 Subject: [PATCH 05/12] Update dhi-backstage.md --- content/guides/dhi-backstage.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/content/guides/dhi-backstage.md b/content/guides/dhi-backstage.md index 67505cb5440..44b0a4bfc0e 100644 --- a/content/guides/dhi-backstage.md +++ b/content/guides/dhi-backstage.md @@ -283,14 +283,25 @@ When you build, you'll see Socket Firewall messages in the build output: `Protec The previous steps still use the `-dev` or `-sfw-dev` variant as the runtime image, which includes a shell and package manager. DHI Enterprise customizations let you start from the base (non-dev) image — which has no shell and no package manager — and add only the runtime libraries and language runtimes your application needs. -> **Important** +> [!IMPORTANT] > -> Only add runtime libraries to the customization, not build tools or language runtimes as system packages. Compile everything in the build stage (which uses the `-dev` variant), copy the pre-built `node_modules` into the runtime image, and use an OCI artifact for any language runtimes needed at runtime. +> When creating a customization, only add what your application needs at runtime: +> +> - **System packages** - add shared libraries (such as `sqlite-libs`) and +> language runtimes from the DHI hardened package feed (such as `python-3.14`). +> Do not add build tools (such as `g++`, `make`, or `python3` from Alpine). +> - **Build tools** - keep these in the `-dev` build stage only. Never add them +> to the runtime customization. +> +> Language runtimes installed from the DHI hardened package feed are patched and +> tracked in the image SBOM, which is why they are acceptable as system packages. +> Build tools from Alpine or Debian package feeds are not hardened and should +> never appear in the runtime image. For Backstage, the runtime image needs: -- **sqlite-libs** — the shared library that the compiled `better-sqlite3` native module links against (added as a system package). -- **Python** — if your Backstage plugins or configuration require Python at runtime. Added as the `python-3.14` system package, which installs Python from the hardened DHI package feed. +- **sqlite-libs** - the shared library that the compiled `better-sqlite3` native module links against (added as a system package). +- **Python** - if your Backstage plugins or configuration require Python at runtime. Added as the `python-3.14` system package, which installs Python from the hardened DHI package feed. Docker will continuously build with SLSA Level 3 compliance and patch these customized images within the guaranteed SLA for CVE patching. From f8e2944c5b68edb194bf9a62c84c85dd6fd4e430 Mon Sep 17 00:00:00 2001 From: "Ajeet Singh Raina, Docker Captain, ARM Innovator" Date: Fri, 13 Mar 2026 16:36:45 +0530 Subject: [PATCH 06/12] Update `dhictl` CLI usage details in documentation Clarified the usage of `dhictl` and added a reference for installation instructions. --- content/guides/dhi-backstage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guides/dhi-backstage.md b/content/guides/dhi-backstage.md index 44b0a4bfc0e..cad1fdee0d9 100644 --- a/content/guides/dhi-backstage.md +++ b/content/guides/dhi-backstage.md @@ -318,7 +318,7 @@ For more information, see [Customize an image](/dhi/how-to/customize/). ### Using the `dhictl` CLI -`dhictl` is Docker's command-line tool for managing Docker Hardened Images. It lets you browse the DHI catalog, mirror images, and create customizations directly from your terminal for integrating DHI into CI/CD pipelines and infrastructure-as-code workflows. You can install `dhictl` as a standalone binary or as a Docker CLI plugin (`docker dhi`); it will also be available by default in Docker Desktop soon. +`dhictl` is Docker's command-line tool for managing Docker Hardened Images. It lets you browse the DHI catalog, mirror images, and create customizations directly from your terminal. You can integrate `dhictl` into CI/CD pipelines and infrastructure-as-code workflows. You can install `dhictl` as a standalone binary or as a Docker CLI plugin (`docker dhi`); for installation instructions, see [Use the DHI CLI](/dhi/how-to/cli/). Rather than writing the customization YAML by hand, use `dhictl` to scaffold a starting point: From d1fb2082ddb7d145b671d6a223ecf85f4c30b92f Mon Sep 17 00:00:00 2001 From: "Ajeet Singh Raina, Docker Captain, ARM Innovator" Date: Fri, 13 Mar 2026 16:42:03 +0530 Subject: [PATCH 07/12] Update dhi-backstage.md --- content/guides/dhi-backstage.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/guides/dhi-backstage.md b/content/guides/dhi-backstage.md index cad1fdee0d9..82ce77b6c58 100644 --- a/content/guides/dhi-backstage.md +++ b/content/guides/dhi-backstage.md @@ -288,7 +288,7 @@ The previous steps still use the `-dev` or `-sfw-dev` variant as the runtime ima > When creating a customization, only add what your application needs at runtime: > > - **System packages** - add shared libraries (such as `sqlite-libs`) and -> language runtimes from the DHI hardened package feed (such as `python-3.14`). +> language runtimes from the DHI catalog (such as `python-3.14`). > Do not add build tools (such as `g++`, `make`, or `python3` from Alpine). > - **Build tools** - keep these in the `-dev` build stage only. Never add them > to the runtime customization. @@ -301,7 +301,7 @@ The previous steps still use the `-dev` or `-sfw-dev` variant as the runtime ima For Backstage, the runtime image needs: - **sqlite-libs** - the shared library that the compiled `better-sqlite3` native module links against (added as a system package). -- **Python** - if your Backstage plugins or configuration require Python at runtime. Added as the `python-3.14` system package, which installs Python from the hardened DHI package feed. +- **Python** - if your Backstage plugins or configuration require Python at runtime. Added as the `python-3.14` system package from the DHI catalog. Unlike `python3` installed via `apk`, this package is patched by Docker and tracked in the image SBOM. Docker will continuously build with SLSA Level 3 compliance and patch these customized images within the guaranteed SLA for CVE patching. From 395be277dc47fe5115a9d0161358f2be634345b1 Mon Sep 17 00:00:00 2001 From: "Ajeet Singh Raina, Docker Captain, ARM Innovator" Date: Fri, 13 Mar 2026 16:44:10 +0530 Subject: [PATCH 08/12] Update dhi-backstage.md --- content/guides/dhi-backstage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guides/dhi-backstage.md b/content/guides/dhi-backstage.md index 82ce77b6c58..447a4c0da68 100644 --- a/content/guides/dhi-backstage.md +++ b/content/guides/dhi-backstage.md @@ -445,7 +445,7 @@ A typical comparison across the approaches shows results similar to the followin > [!NOTE] > -> The `-sfw-dev` variant is larger because Socket Firewall adds monitoring tooling to the image. This is expected — the additional size is in the build stages, and the security benefit during `yarn install` outweighs the size increase. +> The `-sfw-dev` variant is larger because Socket Firewall adds monitoring tooling to the image. The additional size is in the build stages, and the security benefit during `yarn install` outweighs the size increase. For a more thorough assessment, scan with multiple tools: From 18394c7696465884f694f28c834183f6487b52f5 Mon Sep 17 00:00:00 2001 From: "Ajeet Singh Raina, Docker Captain, ARM Innovator" Date: Tue, 17 Mar 2026 09:00:11 +0530 Subject: [PATCH 09/12] Update dhi-backstage.md --- content/guides/dhi-backstage.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/content/guides/dhi-backstage.md b/content/guides/dhi-backstage.md index 447a4c0da68..d6d5e04a907 100644 --- a/content/guides/dhi-backstage.md +++ b/content/guides/dhi-backstage.md @@ -305,7 +305,10 @@ For Backstage, the runtime image needs: Docker will continuously build with SLSA Level 3 compliance and patch these customized images within the guaranteed SLA for CVE patching. -### Using the Docker Hub UI +To create the customization, use one of the following methods. + +{{< tabs >}} +{{< tab name="Docker Hub UI" >}} After you mirror the Node.js DHI repository to your organization's namespace: @@ -316,7 +319,8 @@ After you mirror the Node.js DHI repository to your organization's namespace: For more information, see [Customize an image](/dhi/how-to/customize/). -### Using the `dhictl` CLI +{{< /tab >}} +{{< tab name="dhictl CLI" >}} `dhictl` is Docker's command-line tool for managing Docker Hardened Images. It lets you browse the DHI catalog, mirror images, and create customizations directly from your terminal. You can integrate `dhictl` into CI/CD pipelines and infrastructure-as-code workflows. You can install `dhictl` as a standalone binary or as a Docker CLI plugin (`docker dhi`); for installation instructions, see [Use the DHI CLI](/dhi/how-to/cli/). @@ -380,7 +384,10 @@ Docker builds the customized image on its secure infrastructure and publishes it > > If your Backstage configuration does not require Python at runtime, you can omit the `python-3.14` from the packages list. The `sqlite-libs` package alone is sufficient to run Backstage with `better-sqlite3`. -### Updated Dockerfile +{{< /tab >}} +{{< /tabs >}} + +### Update the Dockerfile Update only the final stage of your Dockerfile to use the customized image: From 66392fd3edbb3120b86122d01ad10c6309b9d2a2 Mon Sep 17 00:00:00 2001 From: "Ajeet Singh Raina, Docker Captain, ARM Innovator" Date: Tue, 17 Mar 2026 09:03:04 +0530 Subject: [PATCH 10/12] Update DHI build stage instructions in guide Clarify the DHI Node.js image variants and installation commands. --- content/guides/dhi-backstage.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/content/guides/dhi-backstage.md b/content/guides/dhi-backstage.md index d6d5e04a907..9d5f5d9077f 100644 --- a/content/guides/dhi-backstage.md +++ b/content/guides/dhi-backstage.md @@ -143,7 +143,10 @@ The `node:24-trixie-slim` image ships with three shells (`dash`, `bash`, and `rb ## Step 2: Switch the build stages to DHI -Replace all three stages with DHI equivalents. DHI Node.js images use Alpine, so the package installation commands change from `apt-get` to `apk`: +Replace all three stages with DHI equivalents. DHI Node.js images are available in both +Alpine and Debian variants. This guide uses the Alpine variant (`dhi.io/node:24-alpine3.23`) +because it produces a smaller image. If you need to stay on Debian for compatibility reasons, +use `dhi.io/node:24-bookworm` and keep `apt-get` instead of `apk`. ```dockerfile # Stage 1: prepare packages From ece8eebacfd80ec012f0fea4bc25cf414418daa7 Mon Sep 17 00:00:00 2001 From: "Ajeet Singh Raina, Docker Captain, ARM Innovator" Date: Tue, 17 Mar 2026 09:08:59 +0530 Subject: [PATCH 11/12] Update dhi-backstage.md --- content/guides/dhi-backstage.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/content/guides/dhi-backstage.md b/content/guides/dhi-backstage.md index 9d5f5d9077f..ec955c9515d 100644 --- a/content/guides/dhi-backstage.md +++ b/content/guides/dhi-backstage.md @@ -403,6 +403,12 @@ COPY --from=build --chown=node:node /app/packages/backend/dist/bundle/ ./ CMD ["node", "packages/backend", "--config", "app-config.yaml"] ``` +Build this version: + +```console +docker build -t backstage:dhi . +``` + Since the customization includes only runtime libraries and OCI artifacts — no build tools, no package manager, no shell — the resulting image is distroless: ```console From 5ab9ab13a1f9b7c55d0707759330b4e2129dd3d3 Mon Sep 17 00:00:00 2001 From: "Ajeet Singh Raina, Docker Captain, ARM Innovator" Date: Tue, 17 Mar 2026 14:13:55 +0530 Subject: [PATCH 12/12] Update dhi-backstage.md --- content/guides/dhi-backstage.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/guides/dhi-backstage.md b/content/guides/dhi-backstage.md index ec955c9515d..06926e48bdd 100644 --- a/content/guides/dhi-backstage.md +++ b/content/guides/dhi-backstage.md @@ -1,7 +1,7 @@ --- title: Secure a Backstage application with Docker Hardened Images description: Secure a Backstage developer portal using Docker Hardened Images, covering native module compilation, Socket Firewall protection, and distroless runtime images. -summary: Learn how to secure a Backstage developer portal using Docker Hardened Images (DHI), handle native module compilation with better-sqlite3, add Socket Firewall protection during dependency installation, and produce a distroless runtime image using DHI Enterprise customizations. +summary: Learn how to secure a Backstage developer portal using Docker Hardened Images (DHI), handle native module compilation with better-sqlite3, add Socket Firewall protection during dependency installation, and produce a distroless runtime image using DHI customizations. keywords: docker hardened images, dhi, backstage, CNCF, developer portal, node.js, native modules, sqlite, better-sqlite3, distroless, socket firewall, dhictl, multi-stage build tags: ["Docker Hardened Images", "dhi"] params: @@ -282,9 +282,9 @@ When you build, you'll see Socket Firewall messages in the build output: `Protec > > The `-sfw-dev` variant is larger (1.9 GB versus 1.72 GB) because Socket Firewall adds monitoring tooling. The security benefit during `yarn install` outweighs the size increase. -## Step 4: Remove the shell and the package manager with DHI Enterprise customizations +## Step 4: Remove the shell and the package manager with DHI customizations -The previous steps still use the `-dev` or `-sfw-dev` variant as the runtime image, which includes a shell and package manager. DHI Enterprise customizations let you start from the base (non-dev) image — which has no shell and no package manager — and add only the runtime libraries and language runtimes your application needs. +The previous steps still use the `-dev` or `-sfw-dev` variant as the runtime image, which includes a shell and package manager. DHI customizations let you start from the base (non-dev) image — which has no shell and no package manager — and add only the runtime libraries and language runtimes your application needs. > [!IMPORTANT] >