From d027ffc589a98cf903ff658fbf01f243f0978e4d Mon Sep 17 00:00:00 2001
From: Brandon Hunt <101275235+brandonh6k@users.noreply.github.com>
Date: Sun, 16 Nov 2025 14:31:46 -0700
Subject: [PATCH 1/3] Update .NET guide to .NET 10
---
content/guides/dotnet/containerize.md | 2 +-
content/guides/dotnet/develop.md | 12 ++++++------
content/guides/dotnet/run-tests.md | 16 ++++++++--------
3 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/content/guides/dotnet/containerize.md b/content/guides/dotnet/containerize.md
index 146874ca9d8c..f160d57a3ce4 100644
--- a/content/guides/dotnet/containerize.md
+++ b/content/guides/dotnet/containerize.md
@@ -60,7 +60,7 @@ Let's get started!
? What application platform does your project use? ASP.NET Core
? What's the name of your solution's main project? myWebApp
-? What version of .NET do you want to use? 8.0
+? What version of .NET do you want to use? 10.0
? What local port do you want to use to access your server? 8080
```
diff --git a/content/guides/dotnet/develop.md b/content/guides/dotnet/develop.md
index d5bea5491fd1..e6689567d49d 100644
--- a/content/guides/dotnet/develop.md
+++ b/content/guides/dotnet/develop.md
@@ -288,11 +288,11 @@ immediately reflected in the running container.
Open `docker-dotnet-sample/src/Pages/Index.cshtml` in an IDE or text editor and update the student name text on line 13 from `Student name is` to `Student name:`.
```diff
--
Student Name is @Model.StudentName
+- Student name is @Model.StudentName
+ Student name: @Model.StudentName
```
-Save the changes to `Index.cshmtl` and then wait a few seconds for the application to rebuild. Refresh [http://localhost:8080](http://localhost:8080) in your browser and verify that the updated text appears.
+Save the changes to `Index.cshtml` and then wait a few seconds for the application to rebuild. Refresh [http://localhost:8080](http://localhost:8080) in your browser and verify that the updated text appears.
Press `ctrl+c` in the terminal to stop your application.
@@ -307,19 +307,19 @@ The following is the updated Dockerfile.
```Dockerfile {hl_lines="10-13"}
# syntax=docker/dockerfile:1
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
ARG TARGETARCH
COPY . /source
WORKDIR /source/src
RUN --mount=type=cache,id=nuget,target=/root/.nuget/packages \
dotnet publish -a ${TARGETARCH/amd64/x64} --use-current-runtime --self-contained false -o /app
-FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS development
+FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS development
COPY . /source
WORKDIR /source/src
CMD dotnet run --no-launch-profile
-FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS final
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final
WORKDIR /app
COPY --from=build /app .
ARG UID=10001
@@ -379,7 +379,7 @@ secrets:
file: db/password.txt
```
-Your containerized application will now use the `mcr.microsoft.com/dotnet/sdk:8.0-alpine` image, which includes development tools like `dotnet test`. Continue to the next section to learn how you can run `dotnet test`.
+Your containerized application will now use the `mcr.microsoft.com/dotnet/sdk:10.0-alpine` image, which includes development tools like `dotnet test`. Continue to the next section to learn how you can run `dotnet test`.
## Summary
diff --git a/content/guides/dotnet/run-tests.md b/content/guides/dotnet/run-tests.md
index 1e404c345965..4d3164488588 100644
--- a/content/guides/dotnet/run-tests.md
+++ b/content/guides/dotnet/run-tests.md
@@ -36,7 +36,7 @@ You should see output that contains the following.
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
-Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1, Duration: < 1 ms - /source/tests/bin/Debug/net8.0/tests.dll (net8.0)
+Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1, Duration: < 1 ms - /source/tests/bin/Debug/net10.0/tests.dll (net10.0)
```
To learn more about the command, see [docker compose run](/reference/cli/docker/compose/run/).
@@ -50,7 +50,7 @@ The following is the updated Dockerfile.
```dockerfile {hl_lines="9"}
# syntax=docker/dockerfile:1
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
ARG TARGETARCH
COPY . /source
WORKDIR /source/src
@@ -58,12 +58,12 @@ RUN --mount=type=cache,id=nuget,target=/root/.nuget/packages \
dotnet publish -a ${TARGETARCH/amd64/x64} --use-current-runtime --self-contained false -o /app
RUN dotnet test /source/tests
-FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS development
+FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS development
COPY . /source
WORKDIR /source/src
CMD dotnet run --no-launch-profile
-FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS final
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final
WORKDIR /app
COPY --from=build /app .
ARG UID=10001
@@ -92,16 +92,16 @@ You should see output containing the following.
#11 1.564 Determining projects to restore...
#11 3.421 Restored /source/src/myWebApp.csproj (in 1.02 sec).
#11 19.42 Restored /source/tests/tests.csproj (in 17.05 sec).
-#11 27.91 myWebApp -> /source/src/bin/Debug/net8.0/myWebApp.dll
-#11 28.47 tests -> /source/tests/bin/Debug/net8.0/tests.dll
-#11 28.49 Test run for /source/tests/bin/Debug/net8.0/tests.dll (.NETCoreApp,Version=v8.0)
+#11 27.91 myWebApp -> /source/src/bin/Debug/net10.0/myWebApp.dll
+#11 28.47 tests -> /source/tests/bin/Debug/net10.0/tests.dll
+#11 28.49 Test run for /source/tests/bin/Debug/net10.0/tests.dll (.NETCoreApp,Version=v10.0)
#11 28.67 Microsoft (R) Test Execution Command Line Tool Version 17.3.3 (x64)
#11 28.67 Copyright (c) Microsoft Corporation. All rights reserved.
#11 28.68
#11 28.97 Starting test execution, please wait...
#11 29.03 A total of 1 test files matched the specified pattern.
#11 32.07
-#11 32.08 Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1, Duration: < 1 ms - /source/tests/bin/Debug/net8.0/tests.dll (net8.0)
+#11 32.08 Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1, Duration: < 1 ms - /source/tests/bin/Debug/net10.0/tests.dll (net10.0)
#11 DONE 32.2s
```
From 613c97585f55525951e241a4f54c2c2333478df9 Mon Sep 17 00:00:00 2001
From: Brandon Hunt <101275235+brandonh6k@users.noreply.github.com>
Date: Tue, 18 Nov 2025 06:00:27 -0700
Subject: [PATCH 2/3] Add Docker Hardened Images support to .NET guide
---
content/guides/dotnet/containerize.md | 105 ++++++++++++++++++++++++--
1 file changed, 98 insertions(+), 7 deletions(-)
diff --git a/content/guides/dotnet/containerize.md b/content/guides/dotnet/containerize.md
index f160d57a3ce4..5cae147baaa6 100644
--- a/content/guides/dotnet/containerize.md
+++ b/content/guides/dotnet/containerize.md
@@ -39,12 +39,71 @@ $ git clone https://github.com/docker/docker-dotnet-sample
## Initialize Docker assets
-Now that you have an application, you can use `docker init` to create the
-necessary Docker assets to containerize your application. Inside the
-`docker-dotnet-sample` directory, run the `docker init` command in a terminal.
-`docker init` provides some default configuration, but you'll need to answer a
-few questions about your application. Refer to the following example to answer
-the prompts from `docker init` and use the same answers for your prompts.
+Now that you have an application, you can create the necessary Docker assets to containerize it. You can choose between using the official .NET images or Docker Hardened Images (DHI).
+
+> [Docker Hardened Images (DHIs)](https://docs.docker.com/dhi/) are minimal, secure, and production-ready container base and application images maintained by Docker. DHI images are recommended for better security—they are designed to reduce vulnerabilities and simplify compliance.
+
+> **Note**: DHI for .NET 10 is not yet available. The following DHI example uses .NET 9. Check the [DHI catalog](https://hub.docker.com/hardened-images/catalog) for .NET 10 availability, or use the official image tab below for .NET 10.
+
+{{< tabs >}}
+{{< tab name="Using Docker Hardened Images (.NET 9)" >}}
+
+Docker Hardened Images (DHIs) for .NET are available on [Docker Hub](https://hub.docker.com/hardened-images/catalog/dhi/aspnetcore). Unlike using the Docker Official Image, you must first mirror the image into your organization. Follow the instructions in the [DHI quickstart](/dhi/get-started/) to create a mirrored repository.
+
+Mirrored repositories must start with `dhi-`, for example: `FROM /dhi-aspnetcore:`.
+
+You can use `docker init` to generate Docker assets, then modify the Dockerfile to use DHI images:
+
+```console
+$ docker init
+Welcome to the Docker Init CLI!
+
+This utility will walk you through creating the following files with sensible defaults for your project:
+ - .dockerignore
+ - Dockerfile
+ - compose.yaml
+ - README.Docker.md
+
+Let's get started!
+
+? What application platform does your project use? ASP.NET Core
+? What's the name of your solution's main project? myWebApp
+? What version of .NET do you want to use? 9.0
+? What local port do you want to use to access your server? 8080
+```
+
+Then update your Dockerfile to use DHI images:
+
+```dockerfile {title=Dockerfile}
+# syntax=docker/dockerfile:1
+
+FROM --platform=$BUILDPLATFORM /dhi-dotnet:9.0-alpine AS build
+ARG TARGETARCH
+COPY . /source
+WORKDIR /source/src
+RUN --mount=type=cache,id=nuget,target=/root/.nuget/packages \
+ dotnet publish -a ${TARGETARCH/amd64/x64} --use-current-runtime --self-contained false -o /app
+
+FROM /dhi-aspnetcore:9.0-alpine AS final
+WORKDIR /app
+COPY --from=build /app .
+ARG UID=10001
+RUN adduser \
+ --disabled-password \
+ --gecos "" \
+ --home "/nonexistent" \
+ --shell "/sbin/nologin" \
+ --no-create-home \
+ --uid "${UID}" \
+ appuser
+USER appuser
+ENTRYPOINT ["dotnet", "myWebApp.dll"]
+```
+
+{{< /tab >}}
+{{< tab name="Using the official .NET 10 image" >}}
+
+You can use `docker init` to create the necessary Docker assets. Inside the `docker-dotnet-sample` directory, run the `docker init` command in a terminal. `docker init` provides some default configuration, but you'll need to answer a few questions about your application. Refer to the following example to answer the prompts from `docker init` and use the same answers for your prompts.
```console
$ docker init
@@ -64,6 +123,37 @@ Let's get started!
? What local port do you want to use to access your server? 8080
```
+This generates a Dockerfile using the official .NET 10 images from Microsoft Container Registry:
+
+```dockerfile {title=Dockerfile}
+# syntax=docker/dockerfile:1
+
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
+ARG TARGETARCH
+COPY . /source
+WORKDIR /source/src
+RUN --mount=type=cache,id=nuget,target=/root/.nuget/packages \
+ dotnet publish -a ${TARGETARCH/amd64/x64} --use-current-runtime --self-contained false -o /app
+
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final
+WORKDIR /app
+COPY --from=build /app .
+ARG UID=10001
+RUN adduser \
+ --disabled-password \
+ --gecos "" \
+ --home "/nonexistent" \
+ --shell "/sbin/nologin" \
+ --no-create-home \
+ --uid "${UID}" \
+ appuser
+USER appuser
+ENTRYPOINT ["dotnet", "myWebApp.dll"]
+```
+
+{{< /tab >}}
+{{< /tabs >}}
+
You should now have the following contents in your `docker-dotnet-sample`
directory.
@@ -78,7 +168,7 @@ directory.
│ └── README.md
```
-To learn more about the files that `docker init` added, see the following:
+To learn more about the files, see the following:
- [Dockerfile](/reference/dockerfile.md)
- [.dockerignore](/reference/dockerfile.md#dockerignore-file)
- [compose.yaml](/reference/compose-file/_index.md)
@@ -126,6 +216,7 @@ Related information:
- [Dockerfile reference](/reference/dockerfile.md)
- [.dockerignore file reference](/reference/dockerfile.md#dockerignore-file)
- [Docker Compose overview](/manuals/compose/_index.md)
+ - [Docker Hardened Images](/dhi/)
## Next steps
From 6641dc552ef56686c62cb32a0d5ab05556e06266 Mon Sep 17 00:00:00 2001
From: Brandon Hunt <101275235+brandonh6k@users.noreply.github.com>
Date: Thu, 4 Dec 2025 09:49:38 -0700
Subject: [PATCH 3/3] Fix .NET DHI guide: update to v10, correct image tags,
add consistent DHI tabs
---
content/guides/dotnet/containerize.md | 24 +++++++-------------
content/guides/dotnet/develop.md | 32 ++++++++++++++++++++++++++-
content/guides/dotnet/run-tests.md | 31 ++++++++++++++++++++++++++
3 files changed, 70 insertions(+), 17 deletions(-)
diff --git a/content/guides/dotnet/containerize.md b/content/guides/dotnet/containerize.md
index 5cae147baaa6..6a42571c1477 100644
--- a/content/guides/dotnet/containerize.md
+++ b/content/guides/dotnet/containerize.md
@@ -43,10 +43,8 @@ Now that you have an application, you can create the necessary Docker assets to
> [Docker Hardened Images (DHIs)](https://docs.docker.com/dhi/) are minimal, secure, and production-ready container base and application images maintained by Docker. DHI images are recommended for better security—they are designed to reduce vulnerabilities and simplify compliance.
-> **Note**: DHI for .NET 10 is not yet available. The following DHI example uses .NET 9. Check the [DHI catalog](https://hub.docker.com/hardened-images/catalog) for .NET 10 availability, or use the official image tab below for .NET 10.
-
{{< tabs >}}
-{{< tab name="Using Docker Hardened Images (.NET 9)" >}}
+{{< tab name="Using Docker Hardened Images" >}}
Docker Hardened Images (DHIs) for .NET are available on [Docker Hub](https://hub.docker.com/hardened-images/catalog/dhi/aspnetcore). Unlike using the Docker Official Image, you must first mirror the image into your organization. Follow the instructions in the [DHI quickstart](/dhi/get-started/) to create a mirrored repository.
@@ -68,7 +66,7 @@ Let's get started!
? What application platform does your project use? ASP.NET Core
? What's the name of your solution's main project? myWebApp
-? What version of .NET do you want to use? 9.0
+? What version of .NET do you want to use? 10.0
? What local port do you want to use to access your server? 8080
```
@@ -77,29 +75,23 @@ Then update your Dockerfile to use DHI images:
```dockerfile {title=Dockerfile}
# syntax=docker/dockerfile:1
-FROM --platform=$BUILDPLATFORM /dhi-dotnet:9.0-alpine AS build
+FROM --platform=$BUILDPLATFORM /dhi-dotnet:10-sdk AS build
ARG TARGETARCH
COPY . /source
WORKDIR /source/src
RUN --mount=type=cache,id=nuget,target=/root/.nuget/packages \
dotnet publish -a ${TARGETARCH/amd64/x64} --use-current-runtime --self-contained false -o /app
-FROM /dhi-aspnetcore:9.0-alpine AS final
+FROM /dhi-aspnetcore:10
WORKDIR /app
COPY --from=build /app .
-ARG UID=10001
-RUN adduser \
- --disabled-password \
- --gecos "" \
- --home "/nonexistent" \
- --shell "/sbin/nologin" \
- --no-create-home \
- --uid "${UID}" \
- appuser
-USER appuser
ENTRYPOINT ["dotnet", "myWebApp.dll"]
```
+> [!NOTE]
+>
+> DHI runtime images already run as a non-root user (`nonroot`), so there's no need to create a user or specify `USER` in your Dockerfile. This reduces the attack surface and simplifies your configuration.
+
{{< /tab >}}
{{< tab name="Using the official .NET 10 image" >}}
diff --git a/content/guides/dotnet/develop.md b/content/guides/dotnet/develop.md
index e6689567d49d..a30122176741 100644
--- a/content/guides/dotnet/develop.md
+++ b/content/guides/dotnet/develop.md
@@ -304,6 +304,33 @@ Add a new development stage to your Dockerfile and update your `compose.yaml` fi
The following is the updated Dockerfile.
+{{< tabs >}}
+{{< tab name="Using Docker Hardened Images" >}}
+
+```Dockerfile {hl_lines="10-13"}
+# syntax=docker/dockerfile:1
+
+FROM --platform=$BUILDPLATFORM /dhi-dotnet:10-sdk AS build
+ARG TARGETARCH
+COPY . /source
+WORKDIR /source/src
+RUN --mount=type=cache,id=nuget,target=/root/.nuget/packages \
+ dotnet publish -a ${TARGETARCH/amd64/x64} --use-current-runtime --self-contained false -o /app
+
+FROM /dhi-dotnet:10-sdk AS development
+COPY . /source
+WORKDIR /source/src
+CMD dotnet run --no-launch-profile
+
+FROM /dhi-aspnetcore:10
+WORKDIR /app
+COPY --from=build /app .
+ENTRYPOINT ["dotnet", "myWebApp.dll"]
+```
+
+{{< /tab >}}
+{{< tab name="Using the official .NET 10 image" >}}
+
```Dockerfile {hl_lines="10-13"}
# syntax=docker/dockerfile:1
@@ -335,6 +362,9 @@ USER appuser
ENTRYPOINT ["dotnet", "myWebApp.dll"]
```
+{{< /tab >}}
+{{< /tabs >}}
+
The following is the updated `compose.yaml` file.
```yaml {hl_lines=[5,15,16]}
@@ -379,7 +409,7 @@ secrets:
file: db/password.txt
```
-Your containerized application will now use the `mcr.microsoft.com/dotnet/sdk:10.0-alpine` image, which includes development tools like `dotnet test`. Continue to the next section to learn how you can run `dotnet test`.
+Your containerized application will now use the SDK image (either `/dhi-dotnet:10-sdk` for DHI or `mcr.microsoft.com/dotnet/sdk:10.0-alpine` for official images), which includes development tools like `dotnet test`. Continue to the next section to learn how you can run `dotnet test`.
## Summary
diff --git a/content/guides/dotnet/run-tests.md b/content/guides/dotnet/run-tests.md
index 4d3164488588..b96442123196 100644
--- a/content/guides/dotnet/run-tests.md
+++ b/content/guides/dotnet/run-tests.md
@@ -47,6 +47,34 @@ To run your tests when building, you need to update your Dockerfile. You can cre
The following is the updated Dockerfile.
+{{< tabs >}}
+{{< tab name="Using Docker Hardened Images" >}}
+
+```dockerfile {hl_lines="9"}
+# syntax=docker/dockerfile:1
+
+FROM --platform=$BUILDPLATFORM /dhi-dotnet:10-sdk AS build
+ARG TARGETARCH
+COPY . /source
+WORKDIR /source/src
+RUN --mount=type=cache,id=nuget,target=/root/.nuget/packages \
+ dotnet publish -a ${TARGETARCH/amd64/x64} --use-current-runtime --self-contained false -o /app
+RUN dotnet test /source/tests
+
+FROM /dhi-dotnet:10-sdk AS development
+COPY . /source
+WORKDIR /source/src
+CMD dotnet run --no-launch-profile
+
+FROM /dhi-aspnetcore:10
+WORKDIR /app
+COPY --from=build /app .
+ENTRYPOINT ["dotnet", "myWebApp.dll"]
+```
+
+{{< /tab >}}
+{{< tab name="Using the official .NET 10 image" >}}
+
```dockerfile {hl_lines="9"}
# syntax=docker/dockerfile:1
@@ -79,6 +107,9 @@ USER appuser
ENTRYPOINT ["dotnet", "myWebApp.dll"]
```
+{{< /tab >}}
+{{< /tabs >}}
+
Run the following command to build an image using the build stage as the target and view the test results. Include `--progress=plain` to view the build output, `--no-cache` to ensure the tests always run, and `--target build` to target the build stage.
```console