Migrate an existing application to use Docker Hardened Images
This guide helps you migrate your existing Dockerfiles to use Docker Hardened Images (DHIs) manually, or with Gordon. DHIs are minimal and security-focused, which may require adjustments to your base images, build process, and runtime configuration.
This guide focuses on migrating framework images, such as images for building applications from source using languages like Go, Python, or Node.js. If you're migrating application images, such as databases, proxies, or other prebuilt services, many of the same principles still apply.
Migration considerations
DHIs omit common tools such as shells and package managers to reduce the attack surface. They also default to running as a nonroot user. As a result, migrating to DHI typically requires the following changes to your Dockerfile:
Item | Migration note |
---|---|
Base image | Replace your base images in your Dockerfile with a Docker Hardened Image. |
Package management | Images intended for runtime, don't contain package managers. Use package managers only in images with a dev tag. Utilize multi-stage builds and copy necessary artifacts from the build stage to the runtime stage. |
Non-root user | By default, images intended for runtime, run as the nonroot user. Ensure that necessary files and directories are accessible to the nonroot user. |
Multi-stage build | Utilize images with a dev or sdk tags for build stages and non-dev images for runtime. |
TLS certificates | DHIs contain standard TLS certificates by default. There is no need to install TLS certificates. |
Ports | DHIs intented for runtime run as a nonroot user by default. As a result, applications in these images can't bind to privileged ports (below 1024) when running in Kubernetes or in Docker Engine versions older than 20.10. To avoid issues, configure your application to listen on port 1025 or higher inside the container. |
Entry point | DHIs may have different entry points than images such as Docker Official Images. Inspect entry points for DHIs and update your Dockerfile if necessary. |
No shell | DHIs intended for runtime don't contain a shell. Use dev images in build stages to run shell commands and then copy artifacts to the runtime stage. |
For more details and troubleshooting tips, see the Troubleshoot.
Migrate an existing application
The following steps outline the migration process.
Step 1: Update the base image in your Dockerfile
Update the base image in your application’s Dockerfile to a hardened image. This
is typically going to be an image tagged as dev
or sdk
because it has the tools
needed to install packages and dependencies.
The following example diff snippet from a Dockerfile shows the old base image replaced by the new hardened image.
- ## Original base image
- FROM golang:1.22
+ ## Updated to use hardened base image
+ FROM <your-namespace>/dhi-golang:1.22-dev
Step 2: Update the runtime image in your Dockerfile
To ensure that your final image is as minimal as possible, you should use a
multi-stage build. All stages in your
Dockerfile should use a hardened image. While intermediary stages will typically
use images tagged as dev
or sdk
, your final runtime stage should use a runtime image.
Utilize the build stage to compile your application and copy the resulting artifacts to the final runtime stage. This ensures that your final image is minimal and secure.
See the Example Dockerfile migrations section for examples of how to update your Dockerfile.
Example Dockerfile migrations
The following migration examples show a Dockerfile before the migration and after the migration.
Go example
#syntax=docker/dockerfile:1
FROM golang:latest
WORKDIR /app
ADD . ./
RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags="-s -w" --installsuffix cgo -o main .
ENTRYPOINT ["/app/main"]
#syntax=docker/dockerfile:1
# === Build stage: Compile Go application ===
FROM <your-namespace>/dhi-golang:1-alpine3.21-dev AS builder
WORKDIR /app
ADD . ./
RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags="-s -w" --installsuffix cgo -o main .
# === Final stage: Create minimal runtime image ===
FROM <your-namespace>/dhi-golang:1-alpine3.21
WORKDIR /app
COPY --from=builder /app/main /app/main
ENTRYPOINT ["/app/main"]
Node.js example
#syntax=docker/dockerfile:1
FROM node:latest
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY image.jpg ./image.jpg
COPY . .
CMD ["node", "index.js"]
#syntax=docker/dockerfile:1
#=== Build stage: Install dependencies and build application ===#
FROM <your-namespace>/dhi-node:23-alpine3.21-dev AS builder
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY image.jpg ./image.jpg
COPY . .
#=== Final stage: Create minimal runtime image ===#
FROM <your-namespace>/dhi-node:23-alpine3.21
ENV PATH=/app/node_modules/.bin:$PATH
COPY --from=builder --chown=node:node /usr/src/app /app
WORKDIR /app
CMD ["index.js"]
Python example
#syntax=docker/dockerfile:1
FROM python:latest AS builder
ENV LANG=C.UTF-8
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PATH="/app/venv/bin:$PATH"
WORKDIR /app
RUN python -m venv /app/venv
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:latest
WORKDIR /app
ENV PYTHONUNBUFFERED=1
ENV PATH="/app/venv/bin:$PATH"
COPY image.py image.png ./
COPY --from=builder /app/venv /app/venv
ENTRYPOINT [ "python", "/app/image.py" ]
#syntax=docker/dockerfile:1
#=== Build stage: Install dependencies and create virtual environment ===#
FROM <your-namespace>/dhi-python:3.13-alpine3.21-dev AS builder
ENV LANG=C.UTF-8
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PATH="/app/venv/bin:$PATH"
WORKDIR /app
RUN python -m venv /app/venv
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
#=== Final stage: Create minimal runtime image ===#
FROM <your-namespace>/dhi-python:3.13-alpine3.21
WORKDIR /app
ENV PYTHONUNBUFFERED=1
ENV PATH="/app/venv/bin:$PATH"
COPY image.py image.png ./
COPY --from=builder /app/venv /app/venv
ENTRYPOINT [ "python", "/app/image.py" ]
Use Gordon
Alternatively, you can request assistance to Gordon, Docker's AI-powered assistant, to migrate your Dockerfile:
Ensure Gordon is enabled.
In Gordon's Toolbox, ensure Gordon's Developer MCP toolkit is enabled.
In the terminal, navigate to the directory containing your Dockerfile.
Start a conversation with Gordon:
docker ai
Type:
"Migrate my dockerfile to DHI"
Follow the conversation with Gordon. Gordon will edit your Dockerfile, so when it requests access to the filesystem and more, type
yes
to allow Gordon to proceed.NoteTo learn more about Gordon's data retention and the data it can access, see Gordon.
When the migration is complete, you see a success message:
The migration to Docker Hardened Images (DHI) is complete. The updated Dockerfile
successfully builds the image, and no vulnerabilities were detected in the final image.
The functionality and optimizations of the original Dockerfile have been preserved.
ImportantAs with any AI tool, you must verify Gordon's edits and test your image.