From 9b571776e13d386c64f1192f9b39c674acb0f7e3 Mon Sep 17 00:00:00 2001 From: David Pine Date: Mon, 6 Jan 2025 09:35:05 -0600 Subject: [PATCH 01/14] Fixes #44146 --- docs/core/docker/publish-as-container.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/core/docker/publish-as-container.md b/docs/core/docker/publish-as-container.md index 37446fc24131e..da6d463cc48fb 100644 --- a/docs/core/docker/publish-as-container.md +++ b/docs/core/docker/publish-as-container.md @@ -1,13 +1,13 @@ --- title: Containerize an app with dotnet publish -description: In this tutorial, you'll learn how to containerize a .NET application with dotnet publish command and without the use of a Dockerfile. -ms.date: 08/13/2024 +description: In this tutorial, you'll learn how to containerize a .NET application with dotnet publish command without the use of a Dockerfile. +ms.date: 01/06/2025 ms.topic: tutorial --- # Containerize a .NET app with dotnet publish -Containers have many features and benefits, such as being an immutable infrastructure, providing a portable architecture, and enabling scalability. The image can be used to create containers for your local development environment, private cloud, or public cloud. In this tutorial, you learn how to containerize a .NET application using the [dotnet publish](../tools/dotnet-publish.md) command without the use of a Dockerfile. Additionally, you explore how to configure the container image and execution, and how to clean up resources. +Containers have many features and benefits, such as being an immutable infrastructure, providing a portable architecture, and enabling scalability. The image can be used to create containers for your local development environment, private cloud, or public cloud. In this tutorial, you learn how to containerize a .NET application using the [dotnet publish](../tools/dotnet-publish.md) command without the use of a Dockerfile. Additionally, you explore how to configure the [container image](container-images.md) and execution, and how to clean up resources. ## Prerequisites @@ -15,7 +15,14 @@ Install the following prerequisites: - [.NET 8+ SDK](https://dotnet.microsoft.com/download/dotnet/8.0)\ If you have .NET installed, use the `dotnet --info` command to determine which SDK you're using. -- [Docker Community Edition](https://www.docker.com/products/docker-desktop) + +If you plan on running the container image locally, you'll also need an OCI-compatible container runtime, such as: + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) +- [Podman](https://podman.io/) + +> [!IMPORTANT] +> The .NET SDK creates container images without Docker. Docker (or Podman) is only needed if you want to run the image locally. Alternatively, you can save the image as a tarball or push it directly to a container registry without using Docker. In addition to these prerequisites, it's recommended that you're familiar with [Worker Services in .NET](../extensions/workers.md). From 49d9f1f68e6f5a3684141f8ed2fa8b3363721365 Mon Sep 17 00:00:00 2001 From: David Pine Date: Mon, 6 Jan 2025 11:00:38 -0600 Subject: [PATCH 02/14] Edit pass and zone pivots for related container content --- docs/core/docker/build-container.md | 198 +++++++++++++++--- docs/core/docker/snippets/8.0/App/Dockerfile | 4 +- docs/core/docker/snippets/8.0/App/Program.cs | 2 + docs/core/docker/snippets/9.0/App/Dockerfile | 15 ++ .../snippets/9.0/App/DotNet.Docker.csproj | 10 + docs/core/docker/snippets/9.0/App/Program.cs | 9 + 6 files changed, 204 insertions(+), 34 deletions(-) create mode 100644 docs/core/docker/snippets/9.0/App/Dockerfile create mode 100644 docs/core/docker/snippets/9.0/App/DotNet.Docker.csproj create mode 100644 docs/core/docker/snippets/9.0/App/Program.cs diff --git a/docs/core/docker/build-container.md b/docs/core/docker/build-container.md index 63528a76b398f..a5fa489059509 100644 --- a/docs/core/docker/build-container.md +++ b/docs/core/docker/build-container.md @@ -4,6 +4,7 @@ description: In this tutorial, you learn how to containerize a .NET application ms.date: 03/20/2024 ms.topic: tutorial ms.custom: "mvc" +zone_pivot_groups: dotnet-version #Customer intent: As a developer, I want to containerize my .NET app so that I can deploy it to the cloud. --- @@ -20,7 +21,7 @@ In this tutorial, you: > - Build a Docker image > - Create and run a Docker container -You explore the Docker container build and deploy tasks for a .NET application. The *Docker platform* uses the *Docker engine* to quickly build and package apps as *Docker images*. These images are written in the *Dockerfile* format to be deployed and run in a layered container. +You explore the Docker container build and deploy tasks for a .NET application. The _Docker platform_ uses the _Docker engine_ to quickly build and package apps as _Docker images_. These images are written in the _Dockerfile_ format to be deployed and run in a layered container. > [!NOTE] > This tutorial **is not** for ASP.NET Core apps. If you're using ASP.NET Core, see the [Learn how to containerize an ASP.NET Core application](/aspnet/core/host-and-deploy/docker/building-net-docker-images) tutorial. @@ -32,11 +33,11 @@ Install the following prerequisites: - [.NET 8+ SDK](https://dotnet.microsoft.com/download/dotnet/8.0).\ If you have .NET installed, use the `dotnet --info` command to determine which SDK you're using. - [Docker Community Edition](https://www.docker.com/products/docker-desktop). -- A temporary working folder for the *Dockerfile* and .NET example app. In this tutorial, the name *docker-working* is used as the working folder. +- A temporary working folder for the _Dockerfile_ and .NET example app. In this tutorial, the name _docker-working_ is used as the working folder. ## Create .NET app -You need a .NET app that the Docker container runs. Open your terminal, create a working folder if you haven't already, and enter it. In the working folder, run the following command to create a new project in a subdirectory named *App*: +You need a .NET app that the Docker container runs. Open your terminal, create a working folder if you haven't already, and enter it. In the working folder, run the following command to create a new project in a subdirectory named _App_: ```dotnetcli dotnet new console -o App -n DotNet.Docker @@ -57,7 +58,7 @@ Your folder tree looks similar to the following directory structure: └── project.nuget.cache ``` -The `dotnet new` command creates a new folder named *App* and generates a "Hello World" console application. Now, you change directories and navigate into the *App* folder from your terminal session. Use the `dotnet run` command to start the app. The application runs, and prints `Hello World!` below the command: +The `dotnet new` command creates a new folder named _App_ and generates a "Hello World" console application. Now, you change directories and navigate into the _App_ folder from your terminal session. Use the `dotnet run` command to start the app. The application runs, and prints `Hello World!` below the command: ```dotnetcli cd App @@ -65,7 +66,7 @@ dotnet run Hello World! ``` -The default template creates an app that prints to the terminal and then immediately terminates. For this tutorial, you use an app that loops indefinitely. Open the *Program.cs* file in a text editor. +The default template creates an app that prints to the terminal and then immediately terminates. For this tutorial, you use an app that loops indefinitely. Open the _Program.cs_ file in a text editor. > [!TIP] > If you're using Visual Studio Code, from the previous terminal session type the following command: @@ -74,9 +75,9 @@ The default template creates an app that prints to the terminal and then immedia > code . > ``` > -> This will open the *App* folder that contains the project in Visual Studio Code. +> This command opens the _App_ folder that contains the project in Visual Studio Code. -The *Program.cs* should look like the following C# code: +The _Program.cs_ should look like the following C# code: ```csharp Console.WriteLine("Hello World!"); @@ -84,8 +85,17 @@ Console.WriteLine("Hello World!"); Replace the file with the following code that counts numbers every second: +:::zone pivot="dotnet-9-0" + +:::code source="snippets/9.0/App/Program.cs"::: + +:::zone-end +:::zone pivot="dotnet-8-0" + :::code source="snippets/8.0/App/Program.cs"::: +:::zone-end + Save the file and test the program again with `dotnet run`. Remember that this app runs indefinitely. Use the cancel command Ctrl+C to stop it. Consider the following example output: ```dotnetcli @@ -97,29 +107,51 @@ Counter: 4 ^C ``` -If you pass a number on the command line to the app, it will only count up to that amount and then exit. Try it with `dotnet run -- 5` to count to five. +If you pass a number on the command line to the app, it limits the count to that amount and then exits. Try it with `dotnet run -- 5` to count to five. > [!IMPORTANT] -> Any parameters after `--` are not passed to the `dotnet run` command and instead are passed to your application. +> Any parameters after `--` aren't passed to the `dotnet run` command and instead are passed to your application. ## Publish .NET app -In order for the app to be suitable for an image creation it has to be built. The `dotnet publish` command is most apt for this, as it builds and publishes the app. For an in-depth reference, see [dotnet build](../tools/dotnet-build.md) and [dotnet publish](../tools/dotnet-publish.md) commands documentation. +In order for the app to be suitable for an image creation it has to compile. The `dotnet publish` command is most apt for this, as it builds and publishes the app. For an in-depth reference, see [dotnet build](../tools/dotnet-build.md) and [dotnet publish](../tools/dotnet-publish.md) commands documentation. ```dotnetcli dotnet publish -c Release ``` -This command compiles your app to the *publish* folder. The path to the *publish* folder from the working folder should be `.\App\bin\Release\net8.0\publish\`. +> [!TIP] +> If you're interested in publishing your .NET app as a container without the need for Docker, see [Containerize a .NET app with dotnet publish](publish-as-container.md). + +The `dotnet publish` command compiles your app to the _publish_ folder. The path to the _publish_ folder from the working folder should be _./App/bin/Release/\/publish/_: #### [Windows](#tab/windows) -From the *App* folder, get a directory listing of the publish folder to verify that the *DotNet.Docker.dll* file was created. +From the _App_ folder, get a directory listing of the publish folder to verify that the _DotNet.Docker.dll_ file was created. + +:::zone pivot="dotnet-9-0" + +```powershell +dir .\bin\Release\net9.0\publish\ + + Directory: C:\Users\default\docker-working\App\bin\Release\net9.0\publish + +Mode LastWriteTime Length Name +---- ------------- ------ ---- +-a---- 1/6/2025 10:11 AM 431 DotNet.Docker.deps.json +-a---- 1/6/2025 10:11 AM 6144 DotNet.Docker.dll +-a---- 1/6/2025 10:11 AM 145408 DotNet.Docker.exe +-a---- 1/6/2025 10:11 AM 11716 DotNet.Docker.pdb +-a---- 1/6/2025 10:11 AM 340 DotNet.Docker.runtimeconfig.json +``` + +:::zone-end +:::zone pivot="dotnet-8-0" ```powershell dir .\bin\Release\net8.0\publish\ - Directory: C:\Users\default\App\bin\Release\net8.0\publish + Directory: C:\Users\default\docker-working\App\bin\Release\net8.0\publish Mode LastWriteTime Length Name ---- ------------- ------ ---- @@ -130,37 +162,99 @@ Mode LastWriteTime Length Name -a--- 9/22/2023 9:17 AM 353 DotNet.Docker.runtimeconfig.json ``` +:::zone-end + #### [Linux](#tab/linux) -Use the `ls` command to get a directory listing and verify that the *DotNet.Docker.dll* file was created. +Use the `ls` command to get a directory listing and verify that the _DotNet.Docker.dll_ file was created. + +:::zone pivot="dotnet-9-0" + +```bash +me@DESKTOP:/docker-working/app$ ls bin/Release/net9.0/publish +DotNet.Docker.deps.json DotNet.Docker.dll DotNet.Docker.exe DotNet.Docker.pdb DotNet.Docker.runtimeconfig.json +``` + +:::zone-end +:::zone pivot="dotnet-8-0" ```bash me@DESKTOP:/docker-working/app$ ls bin/Release/net8.0/publish DotNet.Docker.deps.json DotNet.Docker.dll DotNet.Docker.exe DotNet.Docker.pdb DotNet.Docker.runtimeconfig.json ``` +:::zone-end + --- ## Create the Dockerfile -The *Dockerfile* file is used by the `docker build` command to create a container image. This file is a text file named *Dockerfile* that doesn't have an extension. +The _Dockerfile_ file is used by the `docker build` command to create a container image. This file is a text file named _Dockerfile_ that doesn't have an extension. -Create a file named *Dockerfile* in the directory containing the *.csproj* and open it in a text editor. This tutorial uses the ASP.NET Core runtime image (which contains the .NET runtime image) and corresponds with the .NET console application. +Create a file named _Dockerfile_ in the directory containing the _.csproj_ and open it in a text editor. This tutorial uses the ASP.NET Core runtime image (which contains the .NET runtime image) and corresponds with the .NET console application. + +:::zone pivot="dotnet-9-0" + +:::code language="docker" source="snippets/9.0/App/Dockerfile"::: + +> [!NOTE] +> The ASP.NET Core runtime image is used intentionally here, although the `mcr.microsoft.com/dotnet/runtime:9.0` image could be used instead. + +:::zone-end +:::zone pivot="dotnet-8-0" :::code language="docker" source="snippets/8.0/App/Dockerfile"::: > [!NOTE] -> The ASP.NET Core runtime image is used intentionally here, although the `mcr.microsoft.com/dotnet/runtime:8.0` image could have been used. +> The ASP.NET Core runtime image is used intentionally here, although the `mcr.microsoft.com/dotnet/runtime:8.0` image could be used instead. + +:::zone-end > [!TIP] -> This _Dockerfile_ uses multi-stage builds, which optimizes the final size of the image by layering the build and leaving only required artifacts. For more information, see [Docker Docs: multi-stage builds](https://docs.docker.com/build/building/multi-stage/). +> This _Dockerfile_ uses multi-stage builds, which optimize the final size of the image by layering the build and leaving only required artifacts. For more information, see [Docker Docs: multi-stage builds](https://docs.docker.com/build/building/multi-stage/). -The `FROM` keyword requires a fully qualified Docker container image name. The Microsoft Container Registry (MCR, mcr.microsoft.com) is a syndicate of Docker Hub, which hosts publicly accessible containers. The `dotnet` segment is the container repository, whereas the `sdk` or `aspnet` segment is the container image name. The image is tagged with `8.0`, which is used for versioning. Thus, `mcr.microsoft.com/dotnet/aspnet:8.0` is the .NET 8.0 runtime. Make sure that you pull the runtime version that matches the runtime targeted by your SDK. For example, the app created in the previous section used the .NET 8.0 SDK, and the base image referred to in the *Dockerfile* is tagged with **8.0**. +:::zone pivot="dotnet-9-0" + +The `FROM` keyword requires a fully qualified Docker container image name. The Microsoft Container Registry (MCR, mcr.microsoft.com) is a syndicate of Docker Hub, which hosts publicly accessible containers. The `dotnet` segment is the container repository, whereas the `sdk` or `aspnet` segment is the container image name. The image is tagged with `9.0`, which is used for versioning. Thus, `mcr.microsoft.com/dotnet/aspnet:9.0` is the .NET 9.0 runtime. Make sure that you pull the runtime version that matches the runtime targeted by your SDK. For example, the app created in the previous section used the .NET 9.0 SDK, and the base image referred to in the _Dockerfile_ is tagged with **9.0**. + +> [!IMPORTANT] +> When using Windows-based container images, you need to specify the image tag beyond simply `9.0`, for example, `mcr.microsoft.com/dotnet/aspnet:9.0-nanoserver-1809` instead of `mcr.microsoft.com/dotnet/aspnet:9.0`. Select an image name based on whether you're using Nano Server or Windows Server Core and which version of that OS. You can find a full list of all supported tags on .NET's [Docker Hub page](https://hub.docker.com/_/microsoft-dotnet). + +Save the _Dockerfile_ file. The directory structure of the working folder should look like the following. Some of the deeper-level files and folders are omitted to save space in the article: + +```Directory +πŸ“ docker-working + β””β”€β”€πŸ“‚ App + β”œβ”€β”€ Dockerfile + β”œβ”€β”€ DotNet.Docker.csproj + β”œβ”€β”€ Program.cs + β”œβ”€β”€πŸ“‚ bin + β”‚ β””β”€β”€β”€πŸ“‚ Release + β”‚ β””β”€β”€β”€πŸ“‚ net9.0 + β”‚ β”œβ”€β”€β”€πŸ“‚ publish + β”‚ β”‚ β”œβ”€β”€β”€ DotNet.Docker.deps.json + β”‚ β”‚ β”œβ”€β”€β”€ DotNet.Docker.dll + β”‚ β”‚ β”œβ”€β”€β”€ DotNet.Docker.exe + β”‚ β”‚ β”œβ”€β”€β”€ DotNet.Docker.pdb + β”‚ β”‚ └─── DotNet.Docker.runtimeconfig.json + β”‚ β”œβ”€β”€β”€ DotNet.Docker.deps.json + β”‚ β”œβ”€β”€β”€ DotNet.Docker.dll + β”‚ β”œβ”€β”€β”€ DotNet.Docker.exe + β”‚ β”œβ”€β”€β”€ DotNet.Docker.pdb + β”‚ └─── DotNet.Docker.runtimeconfig.json + β””β”€β”€πŸ“ obj + └──... +``` + +:::zone-end +:::zone pivot="dotnet-8-0" + +The `FROM` keyword requires a fully qualified Docker container image name. The Microsoft Container Registry (MCR, mcr.microsoft.com) is a syndicate of Docker Hub, which hosts publicly accessible containers. The `dotnet` segment is the container repository, whereas the `sdk` or `aspnet` segment is the container image name. The image is tagged with `8.0`, which is used for versioning. Thus, `mcr.microsoft.com/dotnet/aspnet:8.0` is the .NET 8.0 runtime. Make sure that you pull the runtime version that matches the runtime targeted by your SDK. For example, the app created in the previous section used the .NET 8.0 SDK, and the base image referred to in the _Dockerfile_ is tagged with **8.0**. > [!IMPORTANT] > When using Windows-based container images, you need to specify the image tag beyond simply `8.0`, for example, `mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-1809` instead of `mcr.microsoft.com/dotnet/aspnet:8.0`. Select an image name based on whether you're using Nano Server or Windows Server Core and which version of that OS. You can find a full list of all supported tags on .NET's [Docker Hub page](https://hub.docker.com/_/microsoft-dotnet). -Save the *Dockerfile* file. The directory structure of the working folder should look like the following. Some of the deeper-level files and folders have been omitted to save space in the article: +Save the _Dockerfile_ file. The directory structure of the working folder should look like the following. Some of the deeper-level files and folders are omitted to save space in the article: ```Directory πŸ“ docker-working @@ -181,6 +275,8 @@ Save the *Dockerfile* file. The directory structure of the working folder should └──... ``` +:::zone-end + The `ENTRYPOINT` instruction sets `dotnet` as the host for the `DotNet.Docker.dll`. However, it's possible to instead define the `ENTRYPOINT` as the app executable itself, relying on the OS as the app host: ```dockerfile @@ -195,7 +291,17 @@ To build the container, from your terminal, run the following command: docker build -t counter-image -f Dockerfile . ``` -Docker will process each line in the *Dockerfile*. The `.` in the `docker build` command sets the build context of the image. The `-f` switch is the path to the _Dockerfile_. This command builds the image and creates a local repository named **counter-image** that points to that image. After this command finishes, run `docker images` to see a list of images installed: +Docker processes each line in the _Dockerfile_. The `.` in the `docker build` command sets the build context of the image. The `-f` switch is the path to the _Dockerfile_. This command builds the image and creates a local repository named **counter-image** that points to that image. After this command finishes, run `docker images` to see a list of images installed: + +:::zone pivot="dotnet-9-0" + +```console +REPOSITORY TAG IMAGE ID CREATED SIZE +counter-image latest 1c1f1433e51d 32 seconds ago 223MB +``` + +:::zone-end +:::zone pivot="dotnet-8-0" ```console docker images @@ -203,23 +309,39 @@ REPOSITORY TAG IMAGE ID CREATED SIZE counter-image latest 2f15637dc1f6 10 minutes ago 217MB ``` -The `counter-image` repository is the name of the image. The `latest` tag is the tag that is used to identify the image. The `2f15637dc1f6` is the image ID. The `10 minutes ago` is the time the image was created. The `217MB` is the size of the image. The final steps of the _Dockerfile_ are to create a container from the image and run the app, copy the published app to the container, and define the entry point. +:::zone-end + +The `counter-image` repository is the name of the image. Additionally, the image tag, image identifier, size and when it was created are all part of the output. The final steps of the _Dockerfile_ are to create a container from the image and run the app, copy the published app to the container, and define the entry point: + +:::zone pivot="dotnet-9-0" + +```dockerfile +FROM mcr.microsoft.com/dotnet/aspnet:9.0 +WORKDIR /App +COPY --from=build /App/out . +ENTRYPOINT ["dotnet", "DotNet.Docker.dll"] +``` + +:::zone-end +:::zone pivot="dotnet-8-0" ```dockerfile FROM mcr.microsoft.com/dotnet/aspnet:8.0 WORKDIR /App -COPY --from=build-env /App/out . +COPY --from=build /App/out . ENTRYPOINT ["dotnet", "DotNet.Docker.dll"] ``` -The `FROM` command specifies the base image and tag to use. The `WORKDIR` command changes the **current directory** inside of the container to *App*. +:::zone-end -The `COPY` command tells Docker to copy the specified source directory to a destination folder. In this example, the *publish* contents in the `build-env` layer were output into the folder named *App/out*, so it's the source to copy from. All of the published contents in the *App/out* directory are copied into current working directory (*App*). +The `FROM` command specifies the base image and tag to use. The `WORKDIR` command changes the **current directory** inside of the container to _App_. -The next command, `ENTRYPOINT`, tells Docker to configure the container to run as an executable. When the container starts, the `ENTRYPOINT` command runs. When this command ends, the container will automatically stop. +The `COPY` command tells Docker to copy the specified source directory to a destination folder. In this example, the _publish_ contents in the `build` layer are output into the folder named _App/out_, so it's the source to copy from. All of the published contents in the _App/out_ directory are copied into current working directory (_App_). + +The next command, `ENTRYPOINT`, tells Docker to configure the container to run as an executable. When the container starts, the `ENTRYPOINT` command runs. When this command ends, the container automatically stops. > [!TIP] -> Before .NET 8, containers configured to run as read-only may fail with `Failed to create CoreCLR, HRESULT: 0x8007000E`. To address this issue, specify a `DOTNET_EnableDiagnostics` environment variable as `0` (just before the `ENTRYPOINT` step): +> Before .NET 8, containers configured to run as read-only might fail with `Failed to create CoreCLR, HRESULT: 0x8007000E`. To address this issue, specify a `DOTNET_EnableDiagnostics` environment variable as `0` (just before the `ENTRYPOINT` step): > > ```dockerfile > ENV DOTNET_EnableDiagnostics=0 @@ -237,13 +359,13 @@ Now that you have an image that contains your app, you can create a container. Y docker create --name core-counter counter-image ``` -This `docker create` command creates a container based on the **counter-image** image. The output of that command shows you the **CONTAINER ID** (yours will be different) of the created container: +This `docker create` command creates a container based on the **counter-image** image. The output of the `docker create` command shows you the **CONTAINER ID** of the container (your identifier will be different): ```console d0be06126f7db6dd1cee369d911262a353c9b7fb4829a0c11b4b2eb7b2d429cf ``` -To see a list of *all* containers, use the `docker ps -a` command: +To see a list of _all_ containers, use the `docker ps -a` command: ```console docker ps -a @@ -276,7 +398,7 @@ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ### Connect to a container -After a container is running, you can connect to it to see the output. Use the `docker start` and `docker attach` commands to start the container and peek at the output stream. In this example, the Ctrl+C keystroke is used to detach from the running container. This keystroke ends the process in the container unless otherwise specified, which would stop the container. The `--sig-proxy=false` parameter ensures that Ctrl+C won't stop the process in the container. +After a container is running, you can connect to it to see the output. Use the `docker start` and `docker attach` commands to start the container and peek at the output stream. In this example, the Ctrl+C keystroke is used to detach from the running container. This keystroke ends the process in the container unless otherwise specified, which would stop the container. The `--sig-proxy=false` parameter ensures that Ctrl+C doesn't stop the process in the container. After you detach from the container, reattach to verify that it's still running and counting. @@ -351,7 +473,7 @@ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ### Change the ENTRYPOINT -The `docker run` command also lets you modify the `ENTRYPOINT` command from the *Dockerfile* and run something else, but only for that container. For example, use the following command to run `bash` or `cmd.exe`. Edit the command as necessary. +The `docker run` command also lets you modify the `ENTRYPOINT` command from the _Dockerfile_ and run something else, but only for that container. For example, use the following command to run `bash` or `cmd.exe`. Edit the command as necessary. #### [Windows](#tab/windows) @@ -433,13 +555,25 @@ During this tutorial, you created containers and images. If you want, delete the docker rm core-counter ``` -Next, delete any images that you no longer want on your machine. Delete the image created by your *Dockerfile* and then delete the .NET image the *Dockerfile* was based on. You can use the **IMAGE ID** or the **REPOSITORY:TAG** formatted string. +Next, delete any images that you no longer want on your machine. Delete the image created by your _Dockerfile_ and then delete the .NET image the _Dockerfile_ was based on. You can use the **IMAGE ID** or the **REPOSITORY:TAG** formatted string. + +:::zone pivot="dotnet-9-0" + +```console +docker rmi counter-image:latest +docker rmi mcr.microsoft.com/dotnet/aspnet:9.0 +``` + +:::zone-end +:::zone pivot="dotnet-8-0" ```console docker rmi counter-image:latest docker rmi mcr.microsoft.com/dotnet/aspnet:8.0 ``` +:::zone-end + Use the `docker images` command to see a list of images installed. > [!TIP] diff --git a/docs/core/docker/snippets/8.0/App/Dockerfile b/docs/core/docker/snippets/8.0/App/Dockerfile index 01b0a0a805b8a..07ebecb34f0d8 100644 --- a/docs/core/docker/snippets/8.0/App/Dockerfile +++ b/docs/core/docker/snippets/8.0/App/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0@sha256:35792ea4ad1db051981f62b313f1be3b46b1f45cadbaa3c288cd0d3056eefb83 AS build-env +FROM mcr.microsoft.com/dotnet/sdk:8.0@sha256:35792ea4ad1db051981f62b313f1be3b46b1f45cadbaa3c288cd0d3056eefb83 AS build WORKDIR /App # Copy everything @@ -11,5 +11,5 @@ RUN dotnet publish -c Release -o out # Build runtime image FROM mcr.microsoft.com/dotnet/aspnet:8.0@sha256:6c4df091e4e531bb93bdbfe7e7f0998e7ced344f54426b7e874116a3dc3233ff WORKDIR /App -COPY --from=build-env /App/out . +COPY --from=build /App/out . ENTRYPOINT ["dotnet", "DotNet.Docker.dll"] diff --git a/docs/core/docker/snippets/8.0/App/Program.cs b/docs/core/docker/snippets/8.0/App/Program.cs index 20f1c531a343b..82e9ce0b61b99 100644 --- a/docs/core/docker/snippets/8.0/App/Program.cs +++ b/docs/core/docker/snippets/8.0/App/Program.cs @@ -1,7 +1,9 @@ ο»Ώvar counter = 0; var max = args.Length is not 0 ? Convert.ToInt32(args[0]) : -1; + while (max is -1 || counter < max) { Console.WriteLine($"Counter: {++counter}"); + await Task.Delay(TimeSpan.FromMilliseconds(1_000)); } diff --git a/docs/core/docker/snippets/9.0/App/Dockerfile b/docs/core/docker/snippets/9.0/App/Dockerfile new file mode 100644 index 0000000000000..df50fb830f4dd --- /dev/null +++ b/docs/core/docker/snippets/9.0/App/Dockerfile @@ -0,0 +1,15 @@ +FROM mcr.microsoft.com/dotnet/sdk:9.0@sha256:3fcf6f1e809c0553f9feb222369f58749af314af6f063f389cbd2f913b4ad556 AS build +WORKDIR /App + +# Copy everything +COPY . ./ +# Restore as distinct layers +RUN dotnet restore +# Build and publish a release +RUN dotnet publish -c Release -o out + +# Build runtime image +FROM mcr.microsoft.com/dotnet/aspnet:9.0@sha256:b4bea3a52a0a77317fa93c5bbdb076623f81e3e2f201078d89914da71318b5d8 +WORKDIR /App +COPY --from=build /App/out . +ENTRYPOINT ["dotnet", "DotNet.Docker.dll"] diff --git a/docs/core/docker/snippets/9.0/App/DotNet.Docker.csproj b/docs/core/docker/snippets/9.0/App/DotNet.Docker.csproj new file mode 100644 index 0000000000000..fd4bd08da2987 --- /dev/null +++ b/docs/core/docker/snippets/9.0/App/DotNet.Docker.csproj @@ -0,0 +1,10 @@ +ο»Ώ + + + Exe + net9.0 + enable + enable + + + diff --git a/docs/core/docker/snippets/9.0/App/Program.cs b/docs/core/docker/snippets/9.0/App/Program.cs new file mode 100644 index 0000000000000..82e9ce0b61b99 --- /dev/null +++ b/docs/core/docker/snippets/9.0/App/Program.cs @@ -0,0 +1,9 @@ +ο»Ώvar counter = 0; +var max = args.Length is not 0 ? Convert.ToInt32(args[0]) : -1; + +while (max is -1 || counter < max) +{ + Console.WriteLine($"Counter: {++counter}"); + + await Task.Delay(TimeSpan.FromMilliseconds(1_000)); +} From 4f39d60e94987d3877618d3db12165dcd15884a1 Mon Sep 17 00:00:00 2001 From: David Pine Date: Mon, 6 Jan 2025 12:24:07 -0600 Subject: [PATCH 03/14] Fixes #42741 --- docs/core/docker/build-container.md | 6 +++++ .../docker/snippets/9.0/Worker/Program.cs | 7 ++++++ .../9.0/Worker/Properties/launchSettings.json | 15 ++++++++++++ .../core/docker/snippets/9.0/Worker/Worker.cs | 23 +++++++++++++++++++ .../docker/snippets/9.0/Worker/Worker.csproj | 19 +++++++++++++++ .../9.0/Worker/appsettings.Development.json | 8 +++++++ .../snippets/9.0/Worker/appsettings.json | 8 +++++++ 7 files changed, 86 insertions(+) create mode 100644 docs/core/docker/snippets/9.0/Worker/Program.cs create mode 100644 docs/core/docker/snippets/9.0/Worker/Properties/launchSettings.json create mode 100644 docs/core/docker/snippets/9.0/Worker/Worker.cs create mode 100644 docs/core/docker/snippets/9.0/Worker/Worker.csproj create mode 100644 docs/core/docker/snippets/9.0/Worker/appsettings.Development.json create mode 100644 docs/core/docker/snippets/9.0/Worker/appsettings.json diff --git a/docs/core/docker/build-container.md b/docs/core/docker/build-container.md index a5fa489059509..df3424dfcbd04 100644 --- a/docs/core/docker/build-container.md +++ b/docs/core/docker/build-container.md @@ -502,6 +502,9 @@ C:\>dir C:\>^C ``` +> [!NOTE] +> This example only works on Windows containers. Linux containers don't have `cmd.exe`. + #### [Linux](#tab/linux) In this example, `ENTRYPOINT` is changed to `bash`. The `exit` command is run which ends the process and stop the container. @@ -519,6 +522,9 @@ root@9f8de8fbd4a8:/App# exit exit ``` +> [!NOTE] +> This example only works on Linux containers. Windows containers don't have `bash`. + --- ## Essential commands diff --git a/docs/core/docker/snippets/9.0/Worker/Program.cs b/docs/core/docker/snippets/9.0/Worker/Program.cs new file mode 100644 index 0000000000000..1582aaebf0ecc --- /dev/null +++ b/docs/core/docker/snippets/9.0/Worker/Program.cs @@ -0,0 +1,7 @@ +using Worker; + +var builder = Host.CreateApplicationBuilder(args); +builder.Services.AddHostedService(); + +var host = builder.Build(); +host.Run(); diff --git a/docs/core/docker/snippets/9.0/Worker/Properties/launchSettings.json b/docs/core/docker/snippets/9.0/Worker/Properties/launchSettings.json new file mode 100644 index 0000000000000..2252195cbb96a --- /dev/null +++ b/docs/core/docker/snippets/9.0/Worker/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "profiles": { + "Worker": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true + }, + "Container (.NET SDK)": { + "commandName": "SdkContainer" + } + }, + "$schema": "https://json.schemastore.org/launchsettings.json" +} \ No newline at end of file diff --git a/docs/core/docker/snippets/9.0/Worker/Worker.cs b/docs/core/docker/snippets/9.0/Worker/Worker.cs new file mode 100644 index 0000000000000..08fde72c7f29c --- /dev/null +++ b/docs/core/docker/snippets/9.0/Worker/Worker.cs @@ -0,0 +1,23 @@ +namespace Worker; + +public class Worker : BackgroundService +{ + private readonly ILogger _logger; + + public Worker(ILogger logger) + { + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + if (_logger.IsEnabled(LogLevel.Information)) + { + _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); + } + await Task.Delay(1000, stoppingToken); + } + } +} diff --git a/docs/core/docker/snippets/9.0/Worker/Worker.csproj b/docs/core/docker/snippets/9.0/Worker/Worker.csproj new file mode 100644 index 0000000000000..efc5336d24542 --- /dev/null +++ b/docs/core/docker/snippets/9.0/Worker/Worker.csproj @@ -0,0 +1,19 @@ + + + + net9.0 + enable + enable + dotnet-Worker-2ecd8110-3fcc-4ccb-a1d6-952f1adba2c5 + linux-x64 + True + mcr.microsoft.com/dotnet/runtime:9.0 + True + True + . + + + + + + diff --git a/docs/core/docker/snippets/9.0/Worker/appsettings.Development.json b/docs/core/docker/snippets/9.0/Worker/appsettings.Development.json new file mode 100644 index 0000000000000..b2dcdb67421cd --- /dev/null +++ b/docs/core/docker/snippets/9.0/Worker/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/docs/core/docker/snippets/9.0/Worker/appsettings.json b/docs/core/docker/snippets/9.0/Worker/appsettings.json new file mode 100644 index 0000000000000..b2dcdb67421cd --- /dev/null +++ b/docs/core/docker/snippets/9.0/Worker/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} From 65395cd2a11abc226b37453a4b0edcdaa11fe039 Mon Sep 17 00:00:00 2001 From: David Pine Date: Tue, 7 Jan 2025 10:53:32 -0600 Subject: [PATCH 04/14] Address many other open and related issues. --- docs/core/docker/build-container.md | 3 + docs/core/docker/publish-as-container.md | 196 +++++++++++++----- docs/core/docker/snippets/8.0/App/Dockerfile | 2 +- docs/core/docker/snippets/9.0/App/Dockerfile | 2 +- .../docker/snippets/9.0/Worker/Program.cs | 7 - .../9.0/Worker/Properties/launchSettings.json | 15 -- .../core/docker/snippets/9.0/Worker/Worker.cs | 23 -- .../docker/snippets/9.0/Worker/Worker.csproj | 19 -- .../9.0/Worker/appsettings.Development.json | 8 - .../snippets/9.0/Worker/appsettings.json | 8 - .../Worker/DotNet.ContainerImage.csproj | 2 +- .../snippets/{8.0 => }/Worker/Program.cs | 0 .../Worker/Properties/launchSettings.json | 0 .../snippets/{8.0 => }/Worker/Worker.cs | 0 .../Worker/appsettings.Development.json | 0 .../{8.0 => }/Worker/appsettings.json | 0 16 files changed, 145 insertions(+), 140 deletions(-) delete mode 100644 docs/core/docker/snippets/9.0/Worker/Program.cs delete mode 100644 docs/core/docker/snippets/9.0/Worker/Properties/launchSettings.json delete mode 100644 docs/core/docker/snippets/9.0/Worker/Worker.cs delete mode 100644 docs/core/docker/snippets/9.0/Worker/Worker.csproj delete mode 100644 docs/core/docker/snippets/9.0/Worker/appsettings.Development.json delete mode 100644 docs/core/docker/snippets/9.0/Worker/appsettings.json rename docs/core/docker/snippets/{8.0 => }/Worker/DotNet.ContainerImage.csproj (90%) rename docs/core/docker/snippets/{8.0 => }/Worker/Program.cs (100%) rename docs/core/docker/snippets/{8.0 => }/Worker/Properties/launchSettings.json (100%) rename docs/core/docker/snippets/{8.0 => }/Worker/Worker.cs (100%) rename docs/core/docker/snippets/{8.0 => }/Worker/appsettings.Development.json (100%) rename docs/core/docker/snippets/{8.0 => }/Worker/appsettings.json (100%) diff --git a/docs/core/docker/build-container.md b/docs/core/docker/build-container.md index df3424dfcbd04..53a898a5535c3 100644 --- a/docs/core/docker/build-container.md +++ b/docs/core/docker/build-container.md @@ -210,6 +210,9 @@ Create a file named _Dockerfile_ in the directory containing the _.csproj_ and o :::zone-end +> [!IMPORTANT] +> Including a secure hash algorithm (SHA) after the image tag in a _Dockerfile_ is a best practice. This ensures that the image is not tampered with and that the image is the same as the one you expect. The SHA is a unique identifier for the image. For more information, see [Docker Docs: Pull an image by digest](https://docs.docker.com/reference/cli/docker/image/pull/#pull-an-image-by-digest-immutable-identifier). + > [!TIP] > This _Dockerfile_ uses multi-stage builds, which optimize the final size of the image by layering the build and leaving only required artifacts. For more information, see [Docker Docs: multi-stage builds](https://docs.docker.com/build/building/multi-stage/). diff --git a/docs/core/docker/publish-as-container.md b/docs/core/docker/publish-as-container.md index da6d463cc48fb..f5d56409119f1 100644 --- a/docs/core/docker/publish-as-container.md +++ b/docs/core/docker/publish-as-container.md @@ -1,7 +1,7 @@ --- title: Containerize an app with dotnet publish -description: In this tutorial, you'll learn how to containerize a .NET application with dotnet publish command without the use of a Dockerfile. -ms.date: 01/06/2025 +description: In this tutorial, you learn how to containerize a .NET application with dotnet publish command without the use of a Dockerfile. +ms.date: 01/07/2025 ms.topic: tutorial --- @@ -16,25 +16,25 @@ Install the following prerequisites: - [.NET 8+ SDK](https://dotnet.microsoft.com/download/dotnet/8.0)\ If you have .NET installed, use the `dotnet --info` command to determine which SDK you're using. -If you plan on running the container image locally, you'll also need an OCI-compatible container runtime, such as: +If you plan on running the container locally, you need an Open Container Initiative (OCI)-compatible container runtime, such as: -- [Docker Desktop](https://www.docker.com/products/docker-desktop) -- [Podman](https://podman.io/) +- [Docker Desktop](https://www.docker.com/products/docker-desktop): Most common container runtime. +- [Podman](https://podman.io/): An open-source daemonless alternative to Docker. > [!IMPORTANT] -> The .NET SDK creates container images without Docker. Docker (or Podman) is only needed if you want to run the image locally. Alternatively, you can save the image as a tarball or push it directly to a container registry without using Docker. +> The .NET SDK creates container images without Docker. Docker or Podman are only needed if you want to run the image locally. By default, when you [publish your .NET app](#publish-net-app) as a container image it's pushed to a local container runtime. Alternatively, you can save the [image as a tarball](#publish-net-app-to-a-tarball) or push it directly to a [container registry](#publish-net-app-to-container-registry) without using any container runtime at all. -In addition to these prerequisites, it's recommended that you're familiar with [Worker Services in .NET](../extensions/workers.md). +In addition to these prerequisites, it's recommended that you're familiar with [Worker Services in .NET](../extensions/workers.md) as the sample project is a worker. ## Create .NET app -You need a .NET app to containerize, so start by creating a new app from a template. Open your terminal, create a working folder (*sample-directory*) if you haven't already, and change directories so that you're in it. In the working folder, run the following command to create a new project in a subdirectory named *Worker*: +You need a .NET app to containerize, so start by creating a new app from a template. Open your terminal, create a working folder (_sample-directory_) if you haven't already, and change directories so that you're in it. In the working folder, run the following command to create a new project in a subdirectory named _Worker_: ```dotnetcli dotnet new worker -o Worker -n DotNet.ContainerImage ``` -Your folder tree looks like the following: +Your folder tree looks similar to the following directory: ```Directory πŸ“ sample-directory @@ -44,6 +44,8 @@ Your folder tree looks like the following: β”œβ”€β”€DotNet.ContainerImage.csproj β”œβ”€β”€Program.cs β”œβ”€β”€Worker.cs + β”œβ”€β”€πŸ“‚ Properties + β”‚ └─── launchSettings.json β””β”€β”€πŸ“‚ obj β”œβ”€β”€ DotNet.ContainerImage.csproj.nuget.dgspec.json β”œβ”€β”€ DotNet.ContainerImage.csproj.nuget.g.props @@ -52,13 +54,14 @@ Your folder tree looks like the following: └── project.nuget.cache ``` -The `dotnet new` command creates a new folder named _Worker_ and generates a worker service that, when run, logs a message every second. From your terminal session, change directories and navigate into the *Worker* folder. Use the `dotnet run` command to start the app. +The `dotnet new` command creates a new folder named _Worker_ and generates a worker service that, when run, logs a message every second. From your terminal session, change directories and navigate into the _Worker_ folder. Use the `dotnet run` command to start the app. ```dotnetcli dotnet run +Using launch settings from ./Worker/Properties/launchSettings.json... Building... info: DotNet.ContainerImage.Worker[0] - Worker running at: 10/18/2022 08:56:00 -05:00 + Worker running at: 01/06/2025 13:37:28 -06:00 info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down. info: Microsoft.Hosting.Lifetime[0] @@ -66,26 +69,25 @@ info: Microsoft.Hosting.Lifetime[0] info: Microsoft.Hosting.Lifetime[0] Content root path: .\Worker info: DotNet.ContainerImage.Worker[0] - Worker running at: 10/18/2022 08:56:01 -05:00 + Worker running at: 01/06/2025 13:37:29 -06:00 info: DotNet.ContainerImage.Worker[0] - Worker running at: 10/18/2022 08:56:02 -05:00 -info: DotNet.ContainerImage.Worker[0] - Worker running at: 10/18/2022 08:56:03 -05:00 + Worker running at: 01/06/2025 13:37:30 -06:00 info: Microsoft.Hosting.Lifetime[0] Application is shutting down... -Attempting to cancel the build... ``` The worker template loops indefinitely. Use the cancel command Ctrl+C to stop it. -## Add NuGet package +## Publishing project considerations -Starting with .NET SDK version 8.0.200, the `PublishContainer` target is available for every project. To avoid depending on the `Microsoft.NET.Build.Containers` NuGet package, ensure that you're using the latest .NET SDK version. Additionally, your project file needs to have `IsPublishable` set to `true` and enable SDK container support. +Now that you have a .NET app, you can publish it as a container. Before doing so, there are several important considerations to keep in mind. Prior to .NET SDK version 8.0.200, you needed the [πŸ“¦ Microsoft.NET.Build.Containers](https://www.nuget.org/packages/Microsoft.NET.Build.Containers) NuGet package. This package isn't required for .NET SDK version 8.0.200 and later, as the container support is included by default. -> [!IMPORTANT] -> By default, the `IsPublishable` property is set to `true` for `console`, `webapp`, and `worker` templates. +To enable publishing a .NET app as a container, the following build properties are required: -To enable SDK container support, set the `EnableSdkContainerSupport` property to `true` in your project file. +- `IsPublishable`: Set to `true`. This property is implicitly set to `true` for executable project types, such as `console`, `webapp`, and `worker`. +- `EnableSdkContainerSupport`: Set to `true` when your project type is a console app. + +To explicitly enable SDK container support, consider the following project file snippet: ```xml @@ -94,15 +96,47 @@ To enable SDK container support, set the `EnableSdkContainerSupport` property to ``` -## Set the container image name +## Publish switches and build properties + +As with all .NET CLI commands, you can specify [MSBuild properties on the command line](/visualstudio/msbuild/msbuild-command-line-reference). There are many valid syntax forms available to provide properties, such as: + +- `/p:PropertyName=Value` +- `-p:PropertyName=Value` +- `-p PropertyName=Value` +- `--property PropertyName=Value` + +You're free to use whichever syntax you prefer, but the documentation shows example using the `-p` form. + +> [!TIP] +> To help troubleshoot, consider using the MSBuid logs. To generate a binary log (binlog) file, add the `-bl` switch to the `dotnet publish` command. Binlog files are useful for diagnosing build issues and can be opened in the [MSBuild Structured Log Viewer](https://msbuildlog.com/). They provide a detailed trace of the build process, essential for MSBuild analysis. For more information, see [Troubleshoot and create logs for MSBuild](/visualstudio/ide/msbuild-logs#provide-msbuild-binary-logs-for-investigation). + +### Publish profiles and targets + +When using `dotnet publish`, specifying a profile with `-p PublishProfile=DefaultContainer` can set a property that causes the SDK to trigger another target after the publish process. This is an indirect way of achieving the desired result. On the other hand, using `dotnet publish /t:PublishContainer` directly invokes the `PublishContainer` target, achieving the same outcome but in a more straightforward manner. + +In other words, the following .NET CLI command: + +```dotnetcli +dotnet publish -p PublishProfile=DefaultContainer +``` -There are various configuration options available when publishing an app as a container. +Which sets the `PublishProfile` property to `DefaultContainer`, is equivalent to the following command: -By default, the container image name is the `AssemblyName` of the project. If that name is invalid as a container image name, you can override it by specifying a `ContainerRepository` as shown in the following project file: +```dotnetcli +dotnet publish /t:PublishContainer +``` -:::code language="xml" source="snippets/8.0/Worker/DotNet.ContainerImage.csproj" highlight="8"::: +The difference between the two methods is that the former uses a profile to set the property, while the latter directly invokes the target. The reason this is important is that profiles are a feature of MSBuild, and they can be used to set properties in a more complex way than just setting them directly. + +One key issue is that not all project types support profiles or have the same set of profiles available. Additionally, there's a disparity in the level of support for profiles between different tooling, such as Visual Studio and the .NET CLI. Therefore, using targets is generally a clearer and more widely supported method to achieve the same result. + +## Set the container image name -For more information, see [ContainerRepository](#containerrepository). +There are various configuration options available when publishing an app as a container. By default, the container image name is the `AssemblyName` of the project. If that name is invalid as a container image name, you can override it by specifying a `ContainerRepository` as shown in the following project file: + +:::code language="xml" source="snippets/Worker/DotNet.ContainerImage.csproj" highlight="8"::: + +For further reference, see [ContainerRepository](#containerrepository). ## Publish .NET app @@ -118,36 +152,81 @@ The preceding .NET CLI command publishes the app as a container: - Specifying an x64 architecture (`--arch x64`). > [!IMPORTANT] -> To publish the container locally, you must have the Docker daemon running. If it isn't running when you attempt to publish the app as a container, you'll experience an error similar to the following: +> To publish the container locally, you must have an active OCI-compliant daemon running. If it isn't running when you attempt to publish the app as a container, you experience an error similar to the following: > > ```console > ..\build\Microsoft.NET.Build.Containers.targets(66,9): error MSB4018: > The "CreateNewImage" task failed unexpectedly. [..\Worker\DotNet.ContainerImage.csproj] > ``` -The command produces output similar to the example output: +The `dotnet publish` command produces output similar to the example output: ```dotnetcli -Determining projects to restore... - All projects are up-to-date for restore. - DotNet.ContainerImage -> .\Worker\bin\Release\net8.0\linux-x64\DotNet.ContainerImage.dll - DotNet.ContainerImage -> .\Worker\bin\Release\net8.0\linux-x64\publish\ - Building image 'dotnet-worker-image' with tags latest on top of base image mcr.microsoft.com/dotnet/aspnet:8.0 - Pushed container 'dotnet-worker-image:latest' to Docker daemon +Restore complete (0.2s) + DotNet.ContainerImage succeeded (2.6s) β†’ bin\Release\net9.0\linux-x64\publish\ ``` -This command compiles your worker app to the *publish* folder and pushes the container to your local docker registry. +This command compiles your worker app to the _publish_ folder and pushes the container image to your local Docker daemon by default. If you're using Podman, an alias + +## Publish .NET app to a tarball + +A tarball (or tar file) is a file that contains other files. It usually ends with a _*.tar.gz_ compound file extension to help indicate that it's a compressed archive. These file types are used to distribute software or to create backups. In this case, the tarball created is used to distribute a container image. + +To publish a .NET app as a container to a tarball, use the following command: + +```dotnetcli +dotnet publish --os linux --arch x64 \ + /t:PublishContainer \ + -p ContainerArchiveOutputPath=./images/container-image.tar.gz +``` + +The preceding command publishes the app as a container to a tarball: + +- Targeting Linux as the OS (`--os linux`). +- Specifying an x64 architecture (`--arch x64`). +- Setting the `ContainerArchiveOutputPath` property to `./images/container-image.tar.gz`. + +The command doesn't require a running OCI-compliant daemon. For more information, see [ContainerArchiveOutputPath](#containerarchiveoutputpath). + +### Load the tarball + +A common use case for exporting to a tarball is for security-focused organizations. They create containers, export them as tarballs, and then run security-scanning tools over the tarballs. This approach simplifies compliance as it avoids the complexities of scanning a live system. + +The tarball contains the entire container, which can then be loaded using the appropriate tool: + +- [Docker](https://docs.docker.com/reference/cli/docker/image/load/): `docker load -i ./images/container-image.tar.gz` +- [Podman](https://docs.podman.io/en/latest/markdown/podman-load.1.html): `podman load -i ./images/container-image.tar.gz` + +## Publish .NET app to container registry + +Container registries are services that store and manage container images. They're used to store and distribute container images across multiple environments. You can publish a .NET app as a container to a container registry by using the following command: + +```dotnetcli +dotnet publish --os linux --arch x64 \ + /t:PublishContainer \ + -p ContainerRegistry=ghcr.io +``` + +The preceding code publishes the app as a container to a container registry: + +- Targeting Linux as the OS (`--os linux`). +- Specifying an x64 architecture (`--arch x64`). +- Setting the `ContainerRegistry` property to `ghcr.io`. + +For more information, see [ContainerRegistry](#containerregistry). ## Configure container image You can control many aspects of the generated container through MSBuild properties. In general, if you can use a command in a _Dockerfile_ to set some configuration, you can do the same via MSBuild. > [!NOTE] -> The only exceptions to this are `RUN` commands. Due to the way containers are built, those cannot be emulated. If you need this functionality, you'll need to use a _Dockerfile_ to build your container images. +> The only exceptions to this are `RUN` commands. Due to the way containers are built, those can't be emulated. If you need this functionality, you might consider using a _Dockerfile_ to build your container images. + +There's no way of performing `RUN` commands with the .NET SDK. These commands are often used to install some OS packages or create a new OS user, or any number of arbitrary things. If you would like to keep using the .NET SDK container building feature, you can instead create a custom base image with these changes and then using this base image. For more information, see [`ContainerBaseImage`](#containerbaseimage). ### `ContainerArchiveOutputPath` -Starting in .NET 8, you can create a container directly as a _tar.gz_ archive. This feature is useful if your workflow isn't straightforward and requires that you, for example, run a scanning tool over your images before pushing them. Once the archive is created, you can move it, scan it, or load it into a local Docker toolchain. +To create a container image within a _tar.gz_ archive, use the `ContainerArchiveOutputPath` property. This feature is useful if your workflow isn't straightforward and requires that you, for example, run a scanning tool over your images before pushing them. Once the archive is created, you can move it, scan it, or load it into a local Docker toolchain. To publish to an archive, add the `ContainerArchiveOutputPath` property to your `dotnet publish` command, for example: @@ -157,7 +236,7 @@ dotnet publish \ -p ContainerArchiveOutputPath=./images/sdk-container-demo.tar.gz ``` -You can specify either a folder name or a path with a specific file name. If you specify the folder name, the filename generated for the image archive file will be `$(ContainerRepository).tar.gz`. These archives can contain multiple tags inside them, only as single file is created for all `ContainerImageTags`. +You can specify either a folder name or a path with a specific file name. If you specify the folder name, the filename generated for the image archive file is named `$(ContainerRepository).tar.gz`. These archives can contain multiple tags inside them, only as single file is created for all `ContainerImageTags`. ### Container image naming configuration @@ -203,7 +282,7 @@ If you set a value here, you should set the fully qualified name of the image to ``` -Starting with .NET SDK version 8.0.200, the `ContainerBaseImage` inference has been improved to optimize the size and security: +With .NET SDK version 8.0.200, the `ContainerBaseImage` inference is improved to optimize the size and security: - Targeting the `linux-musl-x64` or `linux-musl-arm64` Runtime Identifiers, automatically chooses the `alpine` image variants to ensure your project runs: - If the project uses `PublishAot=true` then the `nightly/runtime-deps` `jammy-chiseled-aot` variant of the base image for best size and security. @@ -227,7 +306,7 @@ This field is free-form, and often can be used to select different operating sys ### `ContainerRuntimeIdentifier` -The container runtime identifier property controls the operating system and architecture used by your container if your [ContainerBaseImage](#containerbaseimage) supports more than one platform. For example, the `mcr.microsoft.com/dotnet/runtime` image currently supports `linux-x64`, `linux-arm`, `linux-arm64` and `win10-x64` images all behind the same tag, so the tooling needs a way to be told which of these versions you intend to use. By default, this is set to the value of the `RuntimeIdentifier` that you chose when you published the container. This property rarely needs to be set explicitly - instead use the `-r` option to the `dotnet publish` command. If the image you've chosen doesn't support the `RuntimeIdentifier` you've chosen, results in an error that describes the RuntimeIdentifiers the image does support. +The `ContainerRuntimeIdentifier` property specifies the OS and architecture for your container if the `ContainerBaseImage` supports multiple platforms. For example, the `mcr.microsoft.com/dotnet/runtime` image supports `linux-x64`, `linux-arm`, `linux-arm64`, and `win10-x64`. By default, this is set to the `RuntimeIdentifier` used when publishing the container. Typically, you don't need to set this property explicitly; instead, use the `-r` option with the `dotnet publish` command. If the chosen image doesn't support the specified `RuntimeIdentifier`, an error indicates the supported identifiers. You can always set the `ContainerBaseImage` property to a fully qualified image name, including the tag, to avoid needing to use this property at all. @@ -251,13 +330,13 @@ The container registry property controls the destination registry, the place tha This tooling supports publishing to any registry that supports the [Docker Registry HTTP API V2](https://docs.docker.com/registry/spec/api/). This includes the following registries explicitly (and likely many more implicitly): -* [Azure Container Registry](https://azure.microsoft.com/products/container-registry) -* [Amazon Elastic Container Registry](https://aws.amazon.com/ecr/) -* [Google Artifact Registry](https://cloud.google.com/artifact-registry) -* [Docker Hub](https://hub.docker.com/) -* [GitHub Packages](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) -* [GitLab-hosted Container Registry](https://docs.gitlab.com/ee/user/packages/container_registry/) -* [Quay.io](https://quay.io/) +- [Azure Container Registry](https://azure.microsoft.com/products/container-registry) +- [Amazon Elastic Container Registry](https://aws.amazon.com/ecr/) +- [Google Artifact Registry](https://cloud.google.com/artifact-registry) +- [Docker Hub](https://hub.docker.com/) +- [GitHub Packages](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) +- [GitLab-hosted Container Registry](https://docs.gitlab.com/ee/user/packages/container_registry/) +- [Quay.io](https://quay.io/) For notes on working with these registries, see the [registry-specific notes](https://aka.ms/dotnet/containers/auth#notes-for-specific-registries). @@ -303,7 +382,7 @@ To specify multiple tags, use a semicolon-delimited set of tags in the `Containe Tags can only contain up to 127 alphanumeric characters, periods, underscores, and dashes. They must start with an alphanumeric character or an underscore. Any other form results in an error being thrown. > [!NOTE] -> When using `ContainerImageTags`, the tags are delimited by a `;` character. If you're calling `dotnet publish` from the command line (as is the case with most CI/CD environments), you'll need to outer wrap the values in a single `'` and inner wrap with double quotes `"`, for example (`='"tag-1;tag-2"'`). Consider the following `dotnet publish` command: +> When using `ContainerImageTags`, the tags are delimited by a `;` character. If you're calling `dotnet publish` from the command line (as is the case with most CI/CD environments), you need to outer wrap the values in a single `'` and inner wrap with double quotes `"`, for example (`='"tag-1;tag-2"'`). Consider the following `dotnet publish` command: > > ```dotnetcli > dotnet publish -p ContainerImageTags='"1.2.3-alpha2;latest"' @@ -314,18 +393,18 @@ Tags can only contain up to 127 alphanumeric characters, periods, underscores, a > [!TIP] > If you experience issues with the `ContainerImageTags` property, consider scoping an environment variable `ContainerImageTags` instead: > -> ```dotnetcli -> ContainerImageTags='1.2.3;latest' dotnet publish +> ```powershell +> $Env:ContainerImageTags='1.2.3;latest'; dotnet publish --os linux --arch x64 /t:PublishContainer > ``` ### `ContainerLabel` -The container label adds a metadata label to the container. Labels have no impact on the container at run time, but are often used to store version and authoring metadata for use by security scanners and other infrastructure tools. You can specify any number of container labels. +The container label adds a metadata label to the container. Labels are often used to store version and authoring metadata for use by security scanners and other infrastructure tools. You can specify any number of container labels. The `ContainerLabel` node has two attributes: - `Include`: The key of the label. -- `Value`: The value of the label (this may be empty). +- `Value`: The value of the label (this might be empty). ```xml @@ -353,7 +432,7 @@ By default, the `/app` directory value is used as the working directory. ### `ContainerPort` -The container port adds TCP or UDP ports to the list of known ports for the container. This enables container runtimes like Docker to map these ports to the host machine automatically. This is often used as documentation for the container, but can also be used to enable automatic port mapping. +The container port adds Transmission Control Protocol (TCP) or User Datagram Protocol (UDP) ports to the list of known ports for the container. This enables container runtimes like Docker to map these ports to the host machine automatically. This is often used as documentation for the container, but can also be used to enable automatic port mapping. The `ContainerPort` node has two attributes: @@ -391,6 +470,9 @@ The `ContainerEnvironmentVariable` node has two attributes: For more information, see [.NET environment variables](../tools/dotnet-environment-variables.md). +> [!NOTE] +> It's currently not possible to set environment variables from the .NET CLI when publishing a container image. For more information, see [GitHub: .NET SDK container builds](https://github.com/dotnet/sdk-container-builds/issues/451). + ## Configure container commands By default, the container tools launch your app using either the generated AppHost binary for your app (if your app uses an AppHost), or the `dotnet` command plus your app's DLL. @@ -408,7 +490,7 @@ For more information, see the following configuration items. ### `ContainerAppCommand` -The app command configuration item is the logical entry point of your app. For most apps, this is the AppHost, the generated executable binary for your app. If your app doesn't generate an AppHost, then this command will typically be `dotnet `. These values are applied after any `ENTRYPOINT` in your base container, or directly if no `ENTRYPOINT` is defined. +The app command configuration item is the logical entry point of your app. For most apps, this is the AppHost, the generated executable binary for your app. If your app doesn't generate an AppHost, then this command is typically `dotnet `. These values are applied after any `ENTRYPOINT` in your base container, or directly if no `ENTRYPOINT` is defined. The `ContainerAppCommand` configuration has a single `Include` property, which represents the command, option, or argument to use in the entrypoint command: @@ -474,7 +556,7 @@ The app command instruction configuration helps control the way the `ContainerEn - If both `ContainerEntrypoint` and `ContainerAppCommand` are present, then `ContainerEntrypoint` becomes the entrypoint, and `ContainerAppCommand` becomes the command. > [!NOTE] -> The `ContainerEntrypoint` and `ContainerEntrypointArgs` configuration items have been deprecated as of .NET 8. +> The `ContainerEntrypoint` and `ContainerEntrypointArgs` configuration items are deprecated as of .NET 8. > [!IMPORTANT] > This is for advanced users-most apps shouldn't need to customize their entrypoint to this degree. For more information and if you'd like to provide use cases for your scenarios, see [GitHub: .NET SDK container builds discussions](https://github.com/dotnet/sdk-container-builds/discussions). @@ -483,14 +565,14 @@ The app command instruction configuration helps control the way the `ContainerEn The user configuration property controls the default user that the container runs as. This is often used to run the container as a non-root user, which is a best practice for security. There are a few constraints for this configuration to be aware of: -- It can take various formsβ€”username, linux user ids, group name, linux group id, `username:groupname`, and other ID variants. +- It can take various formsβ€”username, linux user IDs, group name, linux group ID, `username:groupname`, and other ID variants. - There's no verification that the user or group specified exists on the image. - Changing the user can alter the behavior of the app, especially in regards to things like _File System_ permissions. The default value of this field varies by project TFM and target operating system: - If you're targeting .NET 8 or higher and using the Microsoft runtime images, then: - - on Linux the rootless user `app` is used (though it's referenced by its user ID) + - on Linux, the rootless user `app` is used (though it's referenced by its user ID) - on Windows the rootless user `ContainerUser` is used - Otherwise, no default `ContainerUser` is used diff --git a/docs/core/docker/snippets/8.0/App/Dockerfile b/docs/core/docker/snippets/8.0/App/Dockerfile index 07ebecb34f0d8..49c503343c83d 100644 --- a/docs/core/docker/snippets/8.0/App/Dockerfile +++ b/docs/core/docker/snippets/8.0/App/Dockerfile @@ -6,7 +6,7 @@ COPY . ./ # Restore as distinct layers RUN dotnet restore # Build and publish a release -RUN dotnet publish -c Release -o out +RUN dotnet publish -c Release --property:PublishDir=out # Build runtime image FROM mcr.microsoft.com/dotnet/aspnet:8.0@sha256:6c4df091e4e531bb93bdbfe7e7f0998e7ced344f54426b7e874116a3dc3233ff diff --git a/docs/core/docker/snippets/9.0/App/Dockerfile b/docs/core/docker/snippets/9.0/App/Dockerfile index df50fb830f4dd..012db7f463232 100644 --- a/docs/core/docker/snippets/9.0/App/Dockerfile +++ b/docs/core/docker/snippets/9.0/App/Dockerfile @@ -6,7 +6,7 @@ COPY . ./ # Restore as distinct layers RUN dotnet restore # Build and publish a release -RUN dotnet publish -c Release -o out +RUN dotnet publish -c Release --property:PublishDir=out # Build runtime image FROM mcr.microsoft.com/dotnet/aspnet:9.0@sha256:b4bea3a52a0a77317fa93c5bbdb076623f81e3e2f201078d89914da71318b5d8 diff --git a/docs/core/docker/snippets/9.0/Worker/Program.cs b/docs/core/docker/snippets/9.0/Worker/Program.cs deleted file mode 100644 index 1582aaebf0ecc..0000000000000 --- a/docs/core/docker/snippets/9.0/Worker/Program.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Worker; - -var builder = Host.CreateApplicationBuilder(args); -builder.Services.AddHostedService(); - -var host = builder.Build(); -host.Run(); diff --git a/docs/core/docker/snippets/9.0/Worker/Properties/launchSettings.json b/docs/core/docker/snippets/9.0/Worker/Properties/launchSettings.json deleted file mode 100644 index 2252195cbb96a..0000000000000 --- a/docs/core/docker/snippets/9.0/Worker/Properties/launchSettings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "profiles": { - "Worker": { - "commandName": "Project", - "environmentVariables": { - "DOTNET_ENVIRONMENT": "Development" - }, - "dotnetRunMessages": true - }, - "Container (.NET SDK)": { - "commandName": "SdkContainer" - } - }, - "$schema": "https://json.schemastore.org/launchsettings.json" -} \ No newline at end of file diff --git a/docs/core/docker/snippets/9.0/Worker/Worker.cs b/docs/core/docker/snippets/9.0/Worker/Worker.cs deleted file mode 100644 index 08fde72c7f29c..0000000000000 --- a/docs/core/docker/snippets/9.0/Worker/Worker.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Worker; - -public class Worker : BackgroundService -{ - private readonly ILogger _logger; - - public Worker(ILogger logger) - { - _logger = logger; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - while (!stoppingToken.IsCancellationRequested) - { - if (_logger.IsEnabled(LogLevel.Information)) - { - _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); - } - await Task.Delay(1000, stoppingToken); - } - } -} diff --git a/docs/core/docker/snippets/9.0/Worker/Worker.csproj b/docs/core/docker/snippets/9.0/Worker/Worker.csproj deleted file mode 100644 index efc5336d24542..0000000000000 --- a/docs/core/docker/snippets/9.0/Worker/Worker.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - net9.0 - enable - enable - dotnet-Worker-2ecd8110-3fcc-4ccb-a1d6-952f1adba2c5 - linux-x64 - True - mcr.microsoft.com/dotnet/runtime:9.0 - True - True - . - - - - - - diff --git a/docs/core/docker/snippets/9.0/Worker/appsettings.Development.json b/docs/core/docker/snippets/9.0/Worker/appsettings.Development.json deleted file mode 100644 index b2dcdb67421cd..0000000000000 --- a/docs/core/docker/snippets/9.0/Worker/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} diff --git a/docs/core/docker/snippets/9.0/Worker/appsettings.json b/docs/core/docker/snippets/9.0/Worker/appsettings.json deleted file mode 100644 index b2dcdb67421cd..0000000000000 --- a/docs/core/docker/snippets/9.0/Worker/appsettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} diff --git a/docs/core/docker/snippets/8.0/Worker/DotNet.ContainerImage.csproj b/docs/core/docker/snippets/Worker/DotNet.ContainerImage.csproj similarity index 90% rename from docs/core/docker/snippets/8.0/Worker/DotNet.ContainerImage.csproj rename to docs/core/docker/snippets/Worker/DotNet.ContainerImage.csproj index 49a4f590f520d..2410a64fb67d5 100644 --- a/docs/core/docker/snippets/8.0/Worker/DotNet.ContainerImage.csproj +++ b/docs/core/docker/snippets/Worker/DotNet.ContainerImage.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable dotnet-DotNet.ContainerImage-2e40c179-a00b-4cc9-9785-54266210b7eb diff --git a/docs/core/docker/snippets/8.0/Worker/Program.cs b/docs/core/docker/snippets/Worker/Program.cs similarity index 100% rename from docs/core/docker/snippets/8.0/Worker/Program.cs rename to docs/core/docker/snippets/Worker/Program.cs diff --git a/docs/core/docker/snippets/8.0/Worker/Properties/launchSettings.json b/docs/core/docker/snippets/Worker/Properties/launchSettings.json similarity index 100% rename from docs/core/docker/snippets/8.0/Worker/Properties/launchSettings.json rename to docs/core/docker/snippets/Worker/Properties/launchSettings.json diff --git a/docs/core/docker/snippets/8.0/Worker/Worker.cs b/docs/core/docker/snippets/Worker/Worker.cs similarity index 100% rename from docs/core/docker/snippets/8.0/Worker/Worker.cs rename to docs/core/docker/snippets/Worker/Worker.cs diff --git a/docs/core/docker/snippets/8.0/Worker/appsettings.Development.json b/docs/core/docker/snippets/Worker/appsettings.Development.json similarity index 100% rename from docs/core/docker/snippets/8.0/Worker/appsettings.Development.json rename to docs/core/docker/snippets/Worker/appsettings.Development.json diff --git a/docs/core/docker/snippets/8.0/Worker/appsettings.json b/docs/core/docker/snippets/Worker/appsettings.json similarity index 100% rename from docs/core/docker/snippets/8.0/Worker/appsettings.json rename to docs/core/docker/snippets/Worker/appsettings.json From 3233f7d3652f30c0094cebd99c85035ea46842da Mon Sep 17 00:00:00 2001 From: David Pine Date: Tue, 7 Jan 2025 12:22:47 -0600 Subject: [PATCH 05/14] Apply suggestions from code review Co-authored-by: Chet Husk --- docs/core/docker/publish-as-container.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/docker/publish-as-container.md b/docs/core/docker/publish-as-container.md index f5d56409119f1..9a04a11f39d06 100644 --- a/docs/core/docker/publish-as-container.md +++ b/docs/core/docker/publish-as-container.md @@ -105,7 +105,7 @@ As with all .NET CLI commands, you can specify [MSBuild properties on the comman - `-p PropertyName=Value` - `--property PropertyName=Value` -You're free to use whichever syntax you prefer, but the documentation shows example using the `-p` form. +You're free to use whichever syntax you prefer, but the documentation shows examples using the `-p` form. > [!TIP] > To help troubleshoot, consider using the MSBuid logs. To generate a binary log (binlog) file, add the `-bl` switch to the `dotnet publish` command. Binlog files are useful for diagnosing build issues and can be opened in the [MSBuild Structured Log Viewer](https://msbuildlog.com/). They provide a detailed trace of the build process, essential for MSBuild analysis. For more information, see [Troubleshoot and create logs for MSBuild](/visualstudio/ide/msbuild-logs#provide-msbuild-binary-logs-for-investigation). From 4932cc1c21d668f6c5ffffa1a9aed8801a06cfac Mon Sep 17 00:00:00 2001 From: David Pine Date: Tue, 7 Jan 2025 12:26:57 -0600 Subject: [PATCH 06/14] Revert to -o switch --- docs/core/docker/snippets/8.0/App/Dockerfile | 2 +- docs/core/docker/snippets/9.0/App/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core/docker/snippets/8.0/App/Dockerfile b/docs/core/docker/snippets/8.0/App/Dockerfile index 49c503343c83d..c1678c5d0801a 100644 --- a/docs/core/docker/snippets/8.0/App/Dockerfile +++ b/docs/core/docker/snippets/8.0/App/Dockerfile @@ -6,7 +6,7 @@ COPY . ./ # Restore as distinct layers RUN dotnet restore # Build and publish a release -RUN dotnet publish -c Release --property:PublishDir=out +RUN dotnet publish -o out # Build runtime image FROM mcr.microsoft.com/dotnet/aspnet:8.0@sha256:6c4df091e4e531bb93bdbfe7e7f0998e7ced344f54426b7e874116a3dc3233ff diff --git a/docs/core/docker/snippets/9.0/App/Dockerfile b/docs/core/docker/snippets/9.0/App/Dockerfile index 012db7f463232..25fe2535d57b6 100644 --- a/docs/core/docker/snippets/9.0/App/Dockerfile +++ b/docs/core/docker/snippets/9.0/App/Dockerfile @@ -6,7 +6,7 @@ COPY . ./ # Restore as distinct layers RUN dotnet restore # Build and publish a release -RUN dotnet publish -c Release --property:PublishDir=out +RUN dotnet publish -o out # Build runtime image FROM mcr.microsoft.com/dotnet/aspnet:9.0@sha256:b4bea3a52a0a77317fa93c5bbdb076623f81e3e2f201078d89914da71318b5d8 From 2657cf56771924bce2c4a890eeeebdadda2cac52 Mon Sep 17 00:00:00 2001 From: David Pine Date: Tue, 7 Jan 2025 14:11:44 -0600 Subject: [PATCH 07/14] Add new articles and split a few things out. --- .openpublishing.redirection.core.json | 4 + docs/core/containers/overview.md | 207 +++++++++++++++ .../publish-configuration.md} | 250 +----------------- docs/core/containers/sdk-publish.md | 200 ++++++++++++++ .../Worker/DotNet.ContainerImage.csproj | 0 .../snippets/Worker/Program.cs | 0 .../Worker/Properties/launchSettings.json | 0 .../snippets/Worker/Worker.cs | 0 .../Worker/appsettings.Development.json | 0 .../snippets/Worker/appsettings.json | 0 docs/core/docker/build-container.md | 5 +- docs/navigate/devops-testing/toc.yml | 10 +- 12 files changed, 432 insertions(+), 244 deletions(-) create mode 100644 docs/core/containers/overview.md rename docs/core/{docker/publish-as-container.md => containers/publish-configuration.md} (63%) create mode 100644 docs/core/containers/sdk-publish.md rename docs/core/{docker => containers}/snippets/Worker/DotNet.ContainerImage.csproj (100%) rename docs/core/{docker => containers}/snippets/Worker/Program.cs (100%) rename docs/core/{docker => containers}/snippets/Worker/Properties/launchSettings.json (100%) rename docs/core/{docker => containers}/snippets/Worker/Worker.cs (100%) rename docs/core/{docker => containers}/snippets/Worker/appsettings.Development.json (100%) rename docs/core/{docker => containers}/snippets/Worker/appsettings.json (100%) diff --git a/.openpublishing.redirection.core.json b/.openpublishing.redirection.core.json index 4152011e87128..e9cb0f7e1e7bc 100644 --- a/.openpublishing.redirection.core.json +++ b/.openpublishing.redirection.core.json @@ -1349,6 +1349,10 @@ { "source_path_from_root": "/docs/fundamentals/networking/tcp/tcp-overview.md", "redirect_url": "/dotnet/fundamentals/networking/sockets/tcp-classes" + }, + { + "source_path_from_root": "/docs/core/docker/publish-as-container.md", + "redirect_url": "/dotnet/core/containers/sdk-publish" } ] } diff --git a/docs/core/containers/overview.md b/docs/core/containers/overview.md new file mode 100644 index 0000000000000..2f59244f51702 --- /dev/null +++ b/docs/core/containers/overview.md @@ -0,0 +1,207 @@ +--- +title: .NET SDK container creation overview +description: Learn about the .NET SDK container creation feature, including telemetry, publishing considerations, and build properties. +ms.date: 01/07/2025 +ms.topic: overview +--- + +# .NET SDK container creation overview + +While it's possible to [containerize .NET apps with a _Dockerfile_](../docker/build-container.md), there are compelling reasons for [containerizing apps directly with the .NET SDK](sdk-publish.md). This article provides an overview of the .NET SDK container creation feature, with details related to telemetry, publishing considerations, build properties, and authentication to container registries. + +## Telemetry + +When you publish a .NET app as a container, the .NET SDK container tooling collects and sends usage telemetry about how the tools are used. The collected data is in addition to the [telemetry sent by the .NET CLI](../tools/telemetry.md), but uses the same mechanisms and, importantly, adheres to the same [opt-out](../tools/telemetry#how-to-opt-out) controls. + +The telemetry gathered is intended to be general in nature and not leak any personal informationβ€”the intended purpose is to help measure: + +- Usage of the .NET SDK containerization feature overall. +- Success and failure rates, along with general information about what kinds of failures happen most frequently. +- Usage of specific features of the tech, like publishing to various registry kinds, or how the publish was invoked. + +To opt-out of telemetry, set the `DOTNET_CLI_TELEMETRY_OPTOUT` environment variable to `true`. For more information, see [.NET CLI telemetry](../tools/telemetry.md). + +### Inference telemetry + +The following information about how the base image inference process occurred is logged: + +| Date point | Explanation | Sample value | +|--|--|--| +| `InferencePerformed` | If users are manually specifying base images vs making use of inference. | `true` | +| `TargetFramework` | The `TargetFramework` chosen when doing base image inference. | `net8.0` | +| `BaseImage` | The value of the base image chosen, but only if that base image is one of the Microsoft-produced images. If a user specifies any image other than the Microsoft-produced images on mcr.microsoft.com, this value is null. | `mcr.microsoft.com/dotnet/aspnet` | +| `BaseImageTag` | The value of the tag chosen, but only if that tag is for one of the Microsoft-produced images. If a user specifies any image other than the Microsoft-produced images on mcr.microsoft.com, this value is null. | `8.0` | +| `ContainerFamily` | The value of the `ContainerFamily` property if a user used the `ContainerFamily` feature to pick a 'flavor' of one of our base images. This is only set if the user picked or inferred one of the Microsoft-produced .NET images from mcr.microsoft.com | `jammy-chiseled` | +| `ProjectType` | What kind of project was containerized | `AspNetCore` or `Console` | +| `PublishMode` | How the application was packaged | `Aot`, `Trimmed`, `SelfContained`, or `FrameworkDependent` | +| `IsInvariant` | If the image chosen requires invariant globalization or the user opted into it manually | `true` | +| `TargetRuntime` | The RID that this application was published for | `linux-x64` | + +### Image creation telemetry + +The following information about how the container creation and publishing process occurred is logged: + +| Date point | Explanation | Sample value | +|--|--|--| +| `RemotePullType` | If the base image came from a remote registry, what kind of registry was it? | Azure, AWS, Google, GitHub, DockerHub, MRC, or Other | +| `LocalPullType` | If the base image came from a local source, like a container daemon or a tarball. | Docker, Podman, Tarball | +| `RemotePushType` | If the image was pushed to a remote registry, what kind of registry was it? | Azure, AWS, Google, GitHub, DockerHub, MRC, or Other | +| `LocalPushType` | If the image was pushed to a local destination, what was it? | Docker, Podman, Tarball | + +In addition, if various kinds of errors occur during the process that data is collected about what kind of error it was: + +| Date point | Explanation | Sample value | +|--|--|--| +| `Error` | The kind of error that occurred | `unknown_repository`, `credential_failure`, `rid_mismatch`, `local_load`. | +| `Direction` | If the error is a `credential_failure`, was it to the push or pull registry? | `push` | +| Target RID | If the error was a `rid_mismatch`, what RID was requested | `linux-x64` | +| Available RIDs | If the error was a `rid_mismatch`, what RIDs did the base image support? | `linux-x64,linux-arm64` | + +## Publishing project considerations + +Now that you have a .NET app, you can publish it as a container. Before doing so, there are several important considerations to keep in mind. Prior to .NET SDK version 8.0.200, you needed the [πŸ“¦ Microsoft.NET.Build.Containers](https://www.nuget.org/packages/Microsoft.NET.Build.Containers) NuGet package. This package isn't required for .NET SDK version 8.0.200 and later, as the container support is included by default. + +To enable publishing a .NET app as a container, the following build properties are required: + +- `IsPublishable`: Set to `true`. This property is implicitly set to `true` for executable project types, such as `console`, `webapp`, and `worker`. +- `EnableSdkContainerSupport`: Set to `true` when your project type is a console app. + +To explicitly enable SDK container support, consider the following project file snippet: + +```xml + + true + true + +``` + +## Publish switches and build properties + +As with all .NET CLI commands, you can specify [MSBuild properties on the command line](/visualstudio/msbuild/msbuild-command-line-reference). There are many valid syntax forms available to provide properties, such as: + +- `/p:PropertyName=Value` +- `-p:PropertyName=Value` +- `-p PropertyName=Value` +- `--property PropertyName=Value` + +You're free to use whichever syntax you prefer, but the documentation shows examples using the `-p` form. + +> [!TIP] +> To help troubleshoot, consider using the MSBuid logs. To generate a binary log (binlog) file, add the `-bl` switch to the `dotnet publish` command. Binlog files are useful for diagnosing build issues and can be opened in the [MSBuild Structured Log Viewer](https://msbuildlog.com/). They provide a detailed trace of the build process, essential for MSBuild analysis. For more information, see [Troubleshoot and create logs for MSBuild](/visualstudio/ide/msbuild-logs#provide-msbuild-binary-logs-for-investigation). + +### Publish profiles and targets + +When using `dotnet publish`, specifying a profile with `-p PublishProfile=DefaultContainer` can set a property that causes the SDK to trigger another target after the publish process. This is an indirect way of achieving the desired result. On the other hand, using `dotnet publish /t:PublishContainer` directly invokes the `PublishContainer` target, achieving the same outcome but in a more straightforward manner. + +In other words, the following .NET CLI command: + +```dotnetcli +dotnet publish -p PublishProfile=DefaultContainer +``` + +Which sets the `PublishProfile` property to `DefaultContainer`, is equivalent to the following command: + +```dotnetcli +dotnet publish /t:PublishContainer +``` + +The difference between the two methods is that the former uses a profile to set the property, while the latter directly invokes the target. The reason this is important is that profiles are a feature of MSBuild, and they can be used to set properties in a more complex way than just setting them directly. + +One key issue is that not all project types support profiles or have the same set of profiles available. Additionally, there's a disparity in the level of support for profiles between different tooling, such as Visual Studio and the .NET CLI. Therefore, using targets is generally a clearer and more widely supported method to achieve the same result. + +## Authenticate to container registries + +Interacting with private container registries requires authenticating with those registries. + +Docker has an established pattern with this via the [`docker login`](https://docs.docker.com/engine/reference/commandline/login/) command, which is a way of interacting with a Docker config file that contains rules for authenticating with specific registries. This file, and the authentication types it encodes, are supported by Microsoft.Net.Build.Containers for registry authentication. This should ensure that this package works seamlessly with any registry you can `docker pull` from and `docker push`. This file is normally stored at _~/.docker/config.json_, but it can be specified additionally through the `DOCKER_CONFIG` variable, which points to a directory containing a _config.json_ file. + +## Kinds of authentication + +The _config.json_ file contains three kinds of authentication: + +- [Explicit username/password](#explicit-usernamepassword) +- [Credential helpers](#credential-helpers) +- [System keychain](#system-keychains) + +### Explicit username/password + +The `auths` section of the _config.json_ file is a key/value map between registry names and Base64-encoded username:password strings. In a common Docker scenario, running `docker login -u -p ` creates new items in this map. These credentials are popular in continuous integration (CI) systems, where logging in is done by tokens at the start of a run. However, they are less popular for end-user development machines due to the security risk of having bare credentials in a file. + +### Credential helpers + +The `credHelpers` section of the _config.json_ file is a key/value map between registry names and the names of specific programs that can be used to create and retrieve credentials for that registry. This is often used when particular registries have complex authentication requirements. In order for this kind of authentication to work, you must have an application named `docker-credential-{name}` on your system's `PATH`. These kinds of credentials tend to be secure, but can be hard to set up on development or CI machines. + +### System keychains + +The `credsStore` section is a single string property whose value is the name of a docker credential helper program that knows how to interface with the system's password manager. For Windows this might be `wincred` for example. These are popular with Docker installers for macOS and Windows. + +## Authentication via environment variables + +In some scenarios the standard Docker authentication mechanism described above just doesn't cut it. This tooling has an additional mechanism for providing credentials to registries: environment variables. If environment variables are used, the credential provide mechanism won't be used at all. The following environment variables are supported: + +- `DOTNET_CONTAINER_REGISTRY_UNAME`: This should be the username for the registry. If the password for the registry is a token, then the username should be `""`. +- `DOTNET_CONTAINER_REGISTRY_PWORD`: This should be the password or token for the registry. + +> [!NOTE] +> As of .NET SDK 8.0.400, the environment variables for container operations have been updated. The `SDK_CONTAINER_*` variables are now prefixed with `DOTNET_CONTAINER_*`. + +This mechanism is potentially vulnerable to credential leakage, so it should only be used in scenarios where the other mechanism isn't available. For example, if you're using the SDK Container tooling inside a Docker container itself. In addition, this mechanism isn't namespacedβ€”it attempts to use the same credentials for both the _source_ registry (where your base image is located) and the _destination_ registry (where you're pushing your final image). + +## Using insecure registries + +Most registry access is assumed to be secure, meaning HTTPS is used to interact with the registry. However, not all registries are configured with TLS certificates - especially in situations like a +private corporate registry behind a VPN. To support these use cases, container tools provide ways of declaring that a specific registry uses insecure communication. + +Starting in .NET 8.0.400, the SDK understands these configuration files and formats and will automatically use that configuration to determine if HTTP or HTTPS should be used. +Configuring a registry for insecure communication varies based on your container tool of choice. + +### Docker + +Docker stores its registry configuration in the [daemon configuration](https://docs.docker.com/config/daemon/#configuration-file). To add new insecure registries, new hosts are added to the `"insecure-registries"` array property: + +```json +{ + "insecure-registries": [ + "registry.mycorp.net" + ] +} +``` + +> [!NOTE] +> You must restart the Docker daemon to apply any changes to this file. + +### Podman + +Podman uses a [`registries.conf`](https://podman-desktop.io/docs/containers/registries#setting-up-a-registry-with-an-insecure-certificate) TOML file to store registry connection information. This file typically lives at `/etc/containers/registries.conf`. To add new insecure registries, a TOML section is added to hold the settings for the registry, then the `insecure` option must be set to `true`. + +```toml +[[registry]] +location = "registry.mycorp.net" +insecure = true +``` + +> [!NOTE] +> You must restart Podman to apply any changes to the _registries.conf_ file. + +### Environment variables + +Starting in 9.0.100, the .NET SDK recognizes insecure registries passed through the `DOTNET_CONTAINER_INSECURE_REGISTRIES` environment variable. This variable takes a comma-separated list of domains to treat as insecure in the same manner as the Docker and Podman examples above. + +#### [Windows](#tab/windows) + +```powershell +$Env:DOTNET_CONTAINER_INSECURE_REGISTRIES=localhost:5000,registry.mycorp.com; dotnet publish -t:PublishContainer -p:ContainerRegistry=registry.mycorp.com -p:ContainerBaseImage=localhost:5000/dotnet/runtime:9.0 +``` + +#### [Linux](#tab/linux) + +```bash +DOTNET_CONTAINER_INSECURE_REGISTRIES=localhost:5000,registry.mycorp.com dotnet publish -t:PublishContainer -p:ContainerRegistry=registry.mycorp.com -p:ContainerBaseImage=localhost:5000/dotnet/runtime:9.0 +``` + +--- + +## See also + +- [Publish .NET apps as containers](sdk-publish.md) +- [Containerize a .NET app reference](publish-configuration.md) diff --git a/docs/core/docker/publish-as-container.md b/docs/core/containers/publish-configuration.md similarity index 63% rename from docs/core/docker/publish-as-container.md rename to docs/core/containers/publish-configuration.md index 9a04a11f39d06..61e99e16beffb 100644 --- a/docs/core/docker/publish-as-container.md +++ b/docs/core/containers/publish-configuration.md @@ -1,219 +1,13 @@ --- -title: Containerize an app with dotnet publish -description: In this tutorial, you learn how to containerize a .NET application with dotnet publish command without the use of a Dockerfile. +title: Containerize a .NET app reference +description: Reference material for containerizing a .NET app and configuring the container image. +ms.topic: reference ms.date: 01/07/2025 -ms.topic: tutorial --- -# Containerize a .NET app with dotnet publish +# Containerize a .NET app reference -Containers have many features and benefits, such as being an immutable infrastructure, providing a portable architecture, and enabling scalability. The image can be used to create containers for your local development environment, private cloud, or public cloud. In this tutorial, you learn how to containerize a .NET application using the [dotnet publish](../tools/dotnet-publish.md) command without the use of a Dockerfile. Additionally, you explore how to configure the [container image](container-images.md) and execution, and how to clean up resources. - -## Prerequisites - -Install the following prerequisites: - -- [.NET 8+ SDK](https://dotnet.microsoft.com/download/dotnet/8.0)\ -If you have .NET installed, use the `dotnet --info` command to determine which SDK you're using. - -If you plan on running the container locally, you need an Open Container Initiative (OCI)-compatible container runtime, such as: - -- [Docker Desktop](https://www.docker.com/products/docker-desktop): Most common container runtime. -- [Podman](https://podman.io/): An open-source daemonless alternative to Docker. - -> [!IMPORTANT] -> The .NET SDK creates container images without Docker. Docker or Podman are only needed if you want to run the image locally. By default, when you [publish your .NET app](#publish-net-app) as a container image it's pushed to a local container runtime. Alternatively, you can save the [image as a tarball](#publish-net-app-to-a-tarball) or push it directly to a [container registry](#publish-net-app-to-container-registry) without using any container runtime at all. - -In addition to these prerequisites, it's recommended that you're familiar with [Worker Services in .NET](../extensions/workers.md) as the sample project is a worker. - -## Create .NET app - -You need a .NET app to containerize, so start by creating a new app from a template. Open your terminal, create a working folder (_sample-directory_) if you haven't already, and change directories so that you're in it. In the working folder, run the following command to create a new project in a subdirectory named _Worker_: - -```dotnetcli -dotnet new worker -o Worker -n DotNet.ContainerImage -``` - -Your folder tree looks similar to the following directory: - -```Directory -πŸ“ sample-directory - β””β”€β”€πŸ“‚ Worker - β”œβ”€β”€appsettings.Development.json - β”œβ”€β”€appsettings.json - β”œβ”€β”€DotNet.ContainerImage.csproj - β”œβ”€β”€Program.cs - β”œβ”€β”€Worker.cs - β”œβ”€β”€πŸ“‚ Properties - β”‚ └─── launchSettings.json - β””β”€β”€πŸ“‚ obj - β”œβ”€β”€ DotNet.ContainerImage.csproj.nuget.dgspec.json - β”œβ”€β”€ DotNet.ContainerImage.csproj.nuget.g.props - β”œβ”€β”€ DotNet.ContainerImage.csproj.nuget.g.targets - β”œβ”€β”€ project.assets.json - └── project.nuget.cache -``` - -The `dotnet new` command creates a new folder named _Worker_ and generates a worker service that, when run, logs a message every second. From your terminal session, change directories and navigate into the _Worker_ folder. Use the `dotnet run` command to start the app. - -```dotnetcli -dotnet run -Using launch settings from ./Worker/Properties/launchSettings.json... -Building... -info: DotNet.ContainerImage.Worker[0] - Worker running at: 01/06/2025 13:37:28 -06:00 -info: Microsoft.Hosting.Lifetime[0] - Application started. Press Ctrl+C to shut down. -info: Microsoft.Hosting.Lifetime[0] - Hosting environment: Development -info: Microsoft.Hosting.Lifetime[0] - Content root path: .\Worker -info: DotNet.ContainerImage.Worker[0] - Worker running at: 01/06/2025 13:37:29 -06:00 -info: DotNet.ContainerImage.Worker[0] - Worker running at: 01/06/2025 13:37:30 -06:00 -info: Microsoft.Hosting.Lifetime[0] - Application is shutting down... -``` - -The worker template loops indefinitely. Use the cancel command Ctrl+C to stop it. - -## Publishing project considerations - -Now that you have a .NET app, you can publish it as a container. Before doing so, there are several important considerations to keep in mind. Prior to .NET SDK version 8.0.200, you needed the [πŸ“¦ Microsoft.NET.Build.Containers](https://www.nuget.org/packages/Microsoft.NET.Build.Containers) NuGet package. This package isn't required for .NET SDK version 8.0.200 and later, as the container support is included by default. - -To enable publishing a .NET app as a container, the following build properties are required: - -- `IsPublishable`: Set to `true`. This property is implicitly set to `true` for executable project types, such as `console`, `webapp`, and `worker`. -- `EnableSdkContainerSupport`: Set to `true` when your project type is a console app. - -To explicitly enable SDK container support, consider the following project file snippet: - -```xml - - true - true - -``` - -## Publish switches and build properties - -As with all .NET CLI commands, you can specify [MSBuild properties on the command line](/visualstudio/msbuild/msbuild-command-line-reference). There are many valid syntax forms available to provide properties, such as: - -- `/p:PropertyName=Value` -- `-p:PropertyName=Value` -- `-p PropertyName=Value` -- `--property PropertyName=Value` - -You're free to use whichever syntax you prefer, but the documentation shows examples using the `-p` form. - -> [!TIP] -> To help troubleshoot, consider using the MSBuid logs. To generate a binary log (binlog) file, add the `-bl` switch to the `dotnet publish` command. Binlog files are useful for diagnosing build issues and can be opened in the [MSBuild Structured Log Viewer](https://msbuildlog.com/). They provide a detailed trace of the build process, essential for MSBuild analysis. For more information, see [Troubleshoot and create logs for MSBuild](/visualstudio/ide/msbuild-logs#provide-msbuild-binary-logs-for-investigation). - -### Publish profiles and targets - -When using `dotnet publish`, specifying a profile with `-p PublishProfile=DefaultContainer` can set a property that causes the SDK to trigger another target after the publish process. This is an indirect way of achieving the desired result. On the other hand, using `dotnet publish /t:PublishContainer` directly invokes the `PublishContainer` target, achieving the same outcome but in a more straightforward manner. - -In other words, the following .NET CLI command: - -```dotnetcli -dotnet publish -p PublishProfile=DefaultContainer -``` - -Which sets the `PublishProfile` property to `DefaultContainer`, is equivalent to the following command: - -```dotnetcli -dotnet publish /t:PublishContainer -``` - -The difference between the two methods is that the former uses a profile to set the property, while the latter directly invokes the target. The reason this is important is that profiles are a feature of MSBuild, and they can be used to set properties in a more complex way than just setting them directly. - -One key issue is that not all project types support profiles or have the same set of profiles available. Additionally, there's a disparity in the level of support for profiles between different tooling, such as Visual Studio and the .NET CLI. Therefore, using targets is generally a clearer and more widely supported method to achieve the same result. - -## Set the container image name - -There are various configuration options available when publishing an app as a container. By default, the container image name is the `AssemblyName` of the project. If that name is invalid as a container image name, you can override it by specifying a `ContainerRepository` as shown in the following project file: - -:::code language="xml" source="snippets/Worker/DotNet.ContainerImage.csproj" highlight="8"::: - -For further reference, see [ContainerRepository](#containerrepository). - -## Publish .NET app - -To publish the .NET app as a container, use the following [dotnet publish](../tools/dotnet-publish.md) command: - -```dotnetcli -dotnet publish --os linux --arch x64 /t:PublishContainer -``` - -The preceding .NET CLI command publishes the app as a container: - -- Targeting Linux as the OS (`--os linux`). -- Specifying an x64 architecture (`--arch x64`). - -> [!IMPORTANT] -> To publish the container locally, you must have an active OCI-compliant daemon running. If it isn't running when you attempt to publish the app as a container, you experience an error similar to the following: -> -> ```console -> ..\build\Microsoft.NET.Build.Containers.targets(66,9): error MSB4018: -> The "CreateNewImage" task failed unexpectedly. [..\Worker\DotNet.ContainerImage.csproj] -> ``` - -The `dotnet publish` command produces output similar to the example output: - -```dotnetcli -Restore complete (0.2s) - DotNet.ContainerImage succeeded (2.6s) β†’ bin\Release\net9.0\linux-x64\publish\ -``` - -This command compiles your worker app to the _publish_ folder and pushes the container image to your local Docker daemon by default. If you're using Podman, an alias - -## Publish .NET app to a tarball - -A tarball (or tar file) is a file that contains other files. It usually ends with a _*.tar.gz_ compound file extension to help indicate that it's a compressed archive. These file types are used to distribute software or to create backups. In this case, the tarball created is used to distribute a container image. - -To publish a .NET app as a container to a tarball, use the following command: - -```dotnetcli -dotnet publish --os linux --arch x64 \ - /t:PublishContainer \ - -p ContainerArchiveOutputPath=./images/container-image.tar.gz -``` - -The preceding command publishes the app as a container to a tarball: - -- Targeting Linux as the OS (`--os linux`). -- Specifying an x64 architecture (`--arch x64`). -- Setting the `ContainerArchiveOutputPath` property to `./images/container-image.tar.gz`. - -The command doesn't require a running OCI-compliant daemon. For more information, see [ContainerArchiveOutputPath](#containerarchiveoutputpath). - -### Load the tarball - -A common use case for exporting to a tarball is for security-focused organizations. They create containers, export them as tarballs, and then run security-scanning tools over the tarballs. This approach simplifies compliance as it avoids the complexities of scanning a live system. - -The tarball contains the entire container, which can then be loaded using the appropriate tool: - -- [Docker](https://docs.docker.com/reference/cli/docker/image/load/): `docker load -i ./images/container-image.tar.gz` -- [Podman](https://docs.podman.io/en/latest/markdown/podman-load.1.html): `podman load -i ./images/container-image.tar.gz` - -## Publish .NET app to container registry - -Container registries are services that store and manage container images. They're used to store and distribute container images across multiple environments. You can publish a .NET app as a container to a container registry by using the following command: - -```dotnetcli -dotnet publish --os linux --arch x64 \ - /t:PublishContainer \ - -p ContainerRegistry=ghcr.io -``` - -The preceding code publishes the app as a container to a container registry: - -- Targeting Linux as the OS (`--os linux`). -- Specifying an x64 architecture (`--arch x64`). -- Setting the `ContainerRegistry` property to `ghcr.io`. - -For more information, see [ContainerRegistry](#containerregistry). +In this reference article, you learn how to configure the container image that's generated when you publish a .NET app as a container. This article covers the various properties that you can set to control the image, the execution environment, and the commands that are run when the container starts. ## Configure container image @@ -607,35 +401,7 @@ Labels are often used to provide consistent metadata on container images. This p For more information, see [Implement conventional labels on top of existing label infrastructure](https://github.com/dotnet/sdk-container-builds/issues/96). -## Clean up resources - -In this article, you published a .NET worker as a container image. If you want, delete this resource. Use the `docker images` command to see a list of installed images. - -```console -docker images -``` - -Consider the following example output: - -```console -REPOSITORY TAG IMAGE ID CREATED SIZE -dotnet-worker-image 1.0.0 25aeb97a2e21 12 seconds ago 191MB -``` - -> [!TIP] -> Image files can be large. Typically, you would remove temporary containers you created while testing and developing your app. You usually keep the base images with the runtime installed if you plan on building other images based on that runtime. - -To delete the image, copy the image ID and run the `docker image rm` command: - -```console -docker image rm 25aeb97a2e21 -``` - -## Next steps +## See also -- [Announcing built-in container support for the .NET SDK](https://devblogs.microsoft.com/dotnet/announcing-builtin-container-support-for-the-dotnet-sdk) -- [Tutorial: Containerize a .NET app](build-container.md) -- [.NET container images](container-images.md) -- [Review the Azure services that support containers](https://azure.microsoft.com/overview/containers/) -- [Read about Dockerfile commands](https://docs.docker.com/engine/reference/builder/) -- [Explore the container tools in Visual Studio](/visualstudio/containers/overview) +- [Containerize a .NET app with dotnet publish](sdk-publish.md) +- [.NET container images](../docker/container-images.md) diff --git a/docs/core/containers/sdk-publish.md b/docs/core/containers/sdk-publish.md new file mode 100644 index 0000000000000..47f7ad1f8f3ca --- /dev/null +++ b/docs/core/containers/sdk-publish.md @@ -0,0 +1,200 @@ +--- +title: Containerize an app with dotnet publish +description: In this tutorial, you learn how to containerize a .NET application with dotnet publish command without the use of a Dockerfile. +ms.date: 01/07/2025 +ms.topic: tutorial +--- + +# Containerize a .NET app with dotnet publish + +Containers have many features and benefits, such as being an immutable infrastructure, providing a portable architecture, and enabling scalability. The image can be used to create containers for your local development environment, private cloud, or public cloud. In this tutorial, you learn how to containerize a .NET application using the [dotnet publish](../tools/dotnet-publish.md) command without the use of a Dockerfile. Additionally, you explore how to configure the [container image](container-images.md) and execution, and how to clean up resources. + +> [!TIP] +> If you're interested in using a _Dockerfile_ to containerize your .NET app, see [Tutorial: Containerize a .NET app](../docker/build-container.md). + +## Prerequisites + +Install the following prerequisites: + +- [.NET 8+ SDK](https://dotnet.microsoft.com/download/dotnet/8.0)\ +If you have .NET installed, use the `dotnet --info` command to determine which SDK you're using. + +If you plan on running the container locally, you need an Open Container Initiative (OCI)-compatible container runtime, such as: + +- [Docker Desktop](https://www.docker.com/products/docker-desktop): Most common container runtime. +- [Podman](https://podman.io/): An open-source daemonless alternative to Docker. + +> [!IMPORTANT] +> The .NET SDK creates container images without Docker. Docker or Podman are only needed if you want to run the image locally. By default, when you [publish your .NET app](#publish-net-app) as a container image it's pushed to a local container runtime. Alternatively, you can save the [image as a tarball](#publish-net-app-to-a-tarball) or push it directly to a [container registry](#publish-net-app-to-container-registry) without using any container runtime at all. + +In addition to these prerequisites, it's recommended that you're familiar with [Worker Services in .NET](../extensions/workers.md) as the sample project is a worker. + +## Create .NET app + +You need a .NET app to containerize, so start by creating a new app from a template. Open your terminal, create a working folder (_sample-directory_) if you haven't already, and change directories so that you're in it. In the working folder, run the following command to create a new project in a subdirectory named _Worker_: + +```dotnetcli +dotnet new worker -o Worker -n DotNet.ContainerImage +``` + +Your folder tree looks similar to the following directory: + +```Directory +πŸ“ sample-directory + β””β”€β”€πŸ“‚ Worker + β”œβ”€β”€appsettings.Development.json + β”œβ”€β”€appsettings.json + β”œβ”€β”€DotNet.ContainerImage.csproj + β”œβ”€β”€Program.cs + β”œβ”€β”€Worker.cs + β”œβ”€β”€πŸ“‚ Properties + β”‚ └─── launchSettings.json + β””β”€β”€πŸ“‚ obj + β”œβ”€β”€ DotNet.ContainerImage.csproj.nuget.dgspec.json + β”œβ”€β”€ DotNet.ContainerImage.csproj.nuget.g.props + β”œβ”€β”€ DotNet.ContainerImage.csproj.nuget.g.targets + β”œβ”€β”€ project.assets.json + └── project.nuget.cache +``` + +The `dotnet new` command creates a new folder named _Worker_ and generates a worker service that, when run, logs a message every second. From your terminal session, change directories and navigate into the _Worker_ folder. Use the `dotnet run` command to start the app. + +```dotnetcli +dotnet run +Using launch settings from ./Worker/Properties/launchSettings.json... +Building... +info: DotNet.ContainerImage.Worker[0] + Worker running at: 01/06/2025 13:37:28 -06:00 +info: Microsoft.Hosting.Lifetime[0] + Application started. Press Ctrl+C to shut down. +info: Microsoft.Hosting.Lifetime[0] + Hosting environment: Development +info: Microsoft.Hosting.Lifetime[0] + Content root path: .\Worker +info: DotNet.ContainerImage.Worker[0] + Worker running at: 01/06/2025 13:37:29 -06:00 +info: DotNet.ContainerImage.Worker[0] + Worker running at: 01/06/2025 13:37:30 -06:00 +info: Microsoft.Hosting.Lifetime[0] + Application is shutting down... +``` + +The worker template loops indefinitely. Use the cancel command Ctrl+C to stop it. + +## Set the container image name + +There are various configuration options available when publishing an app as a container. By default, the container image name is the `AssemblyName` of the project. If that name is invalid as a container image name, you can override it by specifying a `ContainerRepository` as shown in the following project file: + +:::code language="xml" source="snippets/Worker/DotNet.ContainerImage.csproj" highlight="8"::: + +For further reference, see [ContainerRepository](#containerrepository). + +## Publish .NET app + +To publish the .NET app as a container, use the following [dotnet publish](../tools/dotnet-publish.md) command: + +```dotnetcli +dotnet publish --os linux --arch x64 /t:PublishContainer +``` + +The preceding .NET CLI command publishes the app as a container: + +- Targeting Linux as the OS (`--os linux`). +- Specifying an x64 architecture (`--arch x64`). + +> [!IMPORTANT] +> To publish the container locally, you must have an active OCI-compliant daemon running. If it isn't running when you attempt to publish the app as a container, you experience an error similar to the following: +> +> ```console +> ..\build\Microsoft.NET.Build.Containers.targets(66,9): error MSB4018: +> The "CreateNewImage" task failed unexpectedly. [..\Worker\DotNet.ContainerImage.csproj] +> ``` + +The `dotnet publish` command produces output similar to the example output: + +```dotnetcli +Restore complete (0.2s) + DotNet.ContainerImage succeeded (2.6s) β†’ bin\Release\net9.0\linux-x64\publish\ +``` + +This command compiles your worker app to the _publish_ folder and pushes the container image to your local Docker daemon by default. If you're using Podman, an alias + +## Publish .NET app to a tarball + +A tarball (or tar file) is a file that contains other files. It usually ends with a _*.tar.gz_ compound file extension to help indicate that it's a compressed archive. These file types are used to distribute software or to create backups. In this case, the tarball created is used to distribute a container image. + +To publish a .NET app as a container to a tarball, use the following command: + +```dotnetcli +dotnet publish --os linux --arch x64 \ + /t:PublishContainer \ + -p ContainerArchiveOutputPath=./images/container-image.tar.gz +``` + +The preceding command publishes the app as a container to a tarball: + +- Targeting Linux as the OS (`--os linux`). +- Specifying an x64 architecture (`--arch x64`). +- Setting the `ContainerArchiveOutputPath` property to `./images/container-image.tar.gz`. + +The command doesn't require a running OCI-compliant daemon. For more information, see [ContainerArchiveOutputPath](#containerarchiveoutputpath). + +### Load the tarball + +A common use case for exporting to a tarball is for security-focused organizations. They create containers, export them as tarballs, and then run security-scanning tools over the tarballs. This approach simplifies compliance as it avoids the complexities of scanning a live system. + +The tarball contains the entire container, which can then be loaded using the appropriate tool: + +- [Docker](https://docs.docker.com/reference/cli/docker/image/load/): `docker load -i ./images/container-image.tar.gz` +- [Podman](https://docs.podman.io/en/latest/markdown/podman-load.1.html): `podman load -i ./images/container-image.tar.gz` + +## Publish .NET app to container registry + +Container registries are services that store and manage container images. They're used to store and distribute container images across multiple environments. You can publish a .NET app as a container to a container registry by using the following command: + +```dotnetcli +dotnet publish --os linux --arch x64 \ + /t:PublishContainer \ + -p ContainerRegistry=ghcr.io +``` + +The preceding code publishes the app as a container to a container registry: + +- Targeting Linux as the OS (`--os linux`). +- Specifying an x64 architecture (`--arch x64`). +- Setting the `ContainerRegistry` property to `ghcr.io`. + +For more information, see [ContainerRegistry](#containerregistry). + +## Clean up resources + +In this article, you published a .NET worker as a container image. If you want, delete this resource. Use the `docker images` command to see a list of installed images. + +```console +docker images +``` + +Consider the following example output: + +```console +REPOSITORY TAG IMAGE ID CREATED SIZE +dotnet-worker-image 1.0.0 25aeb97a2e21 12 seconds ago 191MB +``` + +> [!TIP] +> Image files can be large. Typically, you would remove temporary containers you created while testing and developing your app. You usually keep the base images with the runtime installed if you plan on building other images based on that runtime. + +To delete the image, copy the image ID and run the `docker image rm` command: + +```console +docker image rm 25aeb97a2e21 +``` + +## Next steps + +- [Announcing built-in container support for the .NET SDK](https://devblogs.microsoft.com/dotnet/announcing-builtin-container-support-for-the-dotnet-sdk) +- [Tutorial: Containerize a .NET app](build-container.md) +- [.NET container images](container-images.md) +- [Review the Azure services that support containers](https://azure.microsoft.com/overview/containers/) +- [Read about Dockerfile commands](https://docs.docker.com/engine/reference/builder/) +- [Explore the container tools in Visual Studio](/visualstudio/containers/overview) diff --git a/docs/core/docker/snippets/Worker/DotNet.ContainerImage.csproj b/docs/core/containers/snippets/Worker/DotNet.ContainerImage.csproj similarity index 100% rename from docs/core/docker/snippets/Worker/DotNet.ContainerImage.csproj rename to docs/core/containers/snippets/Worker/DotNet.ContainerImage.csproj diff --git a/docs/core/docker/snippets/Worker/Program.cs b/docs/core/containers/snippets/Worker/Program.cs similarity index 100% rename from docs/core/docker/snippets/Worker/Program.cs rename to docs/core/containers/snippets/Worker/Program.cs diff --git a/docs/core/docker/snippets/Worker/Properties/launchSettings.json b/docs/core/containers/snippets/Worker/Properties/launchSettings.json similarity index 100% rename from docs/core/docker/snippets/Worker/Properties/launchSettings.json rename to docs/core/containers/snippets/Worker/Properties/launchSettings.json diff --git a/docs/core/docker/snippets/Worker/Worker.cs b/docs/core/containers/snippets/Worker/Worker.cs similarity index 100% rename from docs/core/docker/snippets/Worker/Worker.cs rename to docs/core/containers/snippets/Worker/Worker.cs diff --git a/docs/core/docker/snippets/Worker/appsettings.Development.json b/docs/core/containers/snippets/Worker/appsettings.Development.json similarity index 100% rename from docs/core/docker/snippets/Worker/appsettings.Development.json rename to docs/core/containers/snippets/Worker/appsettings.Development.json diff --git a/docs/core/docker/snippets/Worker/appsettings.json b/docs/core/containers/snippets/Worker/appsettings.json similarity index 100% rename from docs/core/docker/snippets/Worker/appsettings.json rename to docs/core/containers/snippets/Worker/appsettings.json diff --git a/docs/core/docker/build-container.md b/docs/core/docker/build-container.md index 53a898a5535c3..43e9b2f5f3326 100644 --- a/docs/core/docker/build-container.md +++ b/docs/core/docker/build-container.md @@ -1,7 +1,7 @@ --- title: Containerize an app with Docker tutorial description: In this tutorial, you learn how to containerize a .NET application with Docker. -ms.date: 03/20/2024 +ms.date: 01/07/2025 ms.topic: tutorial ms.custom: "mvc" zone_pivot_groups: dotnet-version @@ -23,6 +23,9 @@ In this tutorial, you: You explore the Docker container build and deploy tasks for a .NET application. The _Docker platform_ uses the _Docker engine_ to quickly build and package apps as _Docker images_. These images are written in the _Dockerfile_ format to be deployed and run in a layered container. +> [!TIP] +> If you're interested in publishing your .NET app as a container without the need for Docker or Podman, see [Containerize a .NET app with dotnet publish](../containers/sdk-publish.md). + > [!NOTE] > This tutorial **is not** for ASP.NET Core apps. If you're using ASP.NET Core, see the [Learn how to containerize an ASP.NET Core application](/aspnet/core/host-and-deploy/docker/building-net-docker-images) tutorial. diff --git a/docs/navigate/devops-testing/toc.yml b/docs/navigate/devops-testing/toc.yml index 78fa8c1354859..70bec53d2eec0 100644 --- a/docs/navigate/devops-testing/toc.yml +++ b/docs/navigate/devops-testing/toc.yml @@ -559,8 +559,16 @@ items: href: ../../core/rid-catalog.md - name: Resource manifest names href: ../../core/resources/manifest-file-names.md - - name: Docker + - name: Containers items: + - name: Overview + href: ../../core/containers/overview.md + - name: .NET SDK publish container + href: ../../core/containers/sdk-publish.md + - name: .NET SDK publish configuration + href: ../../core/containers/publish-configuration.md + - name: Docker + items: - name: Introduction to .NET and Docker href: ../../core/docker/introduction.md - name: Containerize a .NET app From 423975d053c3cd795284d840da1a2421e5879b3d Mon Sep 17 00:00:00 2001 From: David Pine Date: Tue, 7 Jan 2025 14:21:51 -0600 Subject: [PATCH 08/14] Fix links --- docs/core/compatibility/containers/8.0/aspnet-port.md | 2 +- docs/core/containers/overview.md | 2 +- docs/core/containers/sdk-publish.md | 10 +++++----- docs/core/docker/build-container.md | 4 ++-- docs/core/docker/introduction.md | 2 +- docs/core/tools/dotnet-publish.md | 4 ++-- docs/core/whats-new/dotnet-7.md | 2 +- docs/core/whats-new/dotnet-8/containers.md | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/core/compatibility/containers/8.0/aspnet-port.md b/docs/core/compatibility/containers/8.0/aspnet-port.md index 37e278578b3ee..52cf61eeb10a0 100644 --- a/docs/core/compatibility/containers/8.0/aspnet-port.md +++ b/docs/core/compatibility/containers/8.0/aspnet-port.md @@ -102,6 +102,6 @@ None. ## See also - [New non-root 'app' user in Linux images](app-user.md) -- [Containerize a .NET app](../../../docker/publish-as-container.md) +- [Containerize a .NET app with dotnet publish](../../../containers/sdk-publish.md) - [Blog: Secure your .NET cloud apps with rootless Linux containers](https://devblogs.microsoft.com/dotnet/securing-containers-with-rootless/#switching-to-port-8080) - [Blog: Running non-root .NET containers with Kubernetes](https://devblogs.microsoft.com/dotnet/running-nonroot-kubernetes-with-dotnet/) diff --git a/docs/core/containers/overview.md b/docs/core/containers/overview.md index 2f59244f51702..7ed8b0be9b8b7 100644 --- a/docs/core/containers/overview.md +++ b/docs/core/containers/overview.md @@ -11,7 +11,7 @@ While it's possible to [containerize .NET apps with a _Dockerfile_](../docker/bu ## Telemetry -When you publish a .NET app as a container, the .NET SDK container tooling collects and sends usage telemetry about how the tools are used. The collected data is in addition to the [telemetry sent by the .NET CLI](../tools/telemetry.md), but uses the same mechanisms and, importantly, adheres to the same [opt-out](../tools/telemetry#how-to-opt-out) controls. +When you publish a .NET app as a container, the .NET SDK container tooling collects and sends usage telemetry about how the tools are used. The collected data is in addition to the [telemetry sent by the .NET CLI](../tools/telemetry.md), but uses the same mechanisms and, importantly, adheres to the same [opt-out controls](../tools/telemetry.md#how-to-opt-out). The telemetry gathered is intended to be general in nature and not leak any personal informationβ€”the intended purpose is to help measure: diff --git a/docs/core/containers/sdk-publish.md b/docs/core/containers/sdk-publish.md index 47f7ad1f8f3ca..840c63dc9d7b8 100644 --- a/docs/core/containers/sdk-publish.md +++ b/docs/core/containers/sdk-publish.md @@ -87,7 +87,7 @@ There are various configuration options available when publishing an app as a co :::code language="xml" source="snippets/Worker/DotNet.ContainerImage.csproj" highlight="8"::: -For further reference, see [ContainerRepository](#containerrepository). +For further reference, see [`ContainerRepository`](publish-configuration.md#containerrepository). ## Publish .NET app @@ -137,7 +137,7 @@ The preceding command publishes the app as a container to a tarball: - Specifying an x64 architecture (`--arch x64`). - Setting the `ContainerArchiveOutputPath` property to `./images/container-image.tar.gz`. -The command doesn't require a running OCI-compliant daemon. For more information, see [ContainerArchiveOutputPath](#containerarchiveoutputpath). +The command doesn't require a running OCI-compliant daemon. For more information, see [`ContainerArchiveOutputPath`](publish-configuration.md#containerarchiveoutputpath). ### Load the tarball @@ -164,7 +164,7 @@ The preceding code publishes the app as a container to a container registry: - Specifying an x64 architecture (`--arch x64`). - Setting the `ContainerRegistry` property to `ghcr.io`. -For more information, see [ContainerRegistry](#containerregistry). +For more information, see [ContainerRegistry](publish-configuration.md#containerregistry). ## Clean up resources @@ -193,8 +193,8 @@ docker image rm 25aeb97a2e21 ## Next steps - [Announcing built-in container support for the .NET SDK](https://devblogs.microsoft.com/dotnet/announcing-builtin-container-support-for-the-dotnet-sdk) -- [Tutorial: Containerize a .NET app](build-container.md) -- [.NET container images](container-images.md) +- [Tutorial: Containerize a .NET app](../docker/build-container.md) +- [.NET container images](../docker/container-images.md) - [Review the Azure services that support containers](https://azure.microsoft.com/overview/containers/) - [Read about Dockerfile commands](https://docs.docker.com/engine/reference/builder/) - [Explore the container tools in Visual Studio](/visualstudio/containers/overview) diff --git a/docs/core/docker/build-container.md b/docs/core/docker/build-container.md index 43e9b2f5f3326..7cff5e7ee2c39 100644 --- a/docs/core/docker/build-container.md +++ b/docs/core/docker/build-container.md @@ -124,7 +124,7 @@ dotnet publish -c Release ``` > [!TIP] -> If you're interested in publishing your .NET app as a container without the need for Docker, see [Containerize a .NET app with dotnet publish](publish-as-container.md). +> If you're interested in publishing your .NET app as a container without the need for Docker, see [Containerize a .NET app with dotnet publish](../containers/sdk-publish.md). The `dotnet publish` command compiles your app to the _publish_ folder. The path to the _publish_ folder from the working folder should be _./App/bin/Release/\/publish/_: @@ -593,7 +593,7 @@ Use the `docker images` command to see a list of images installed. ## Next steps -- [Containerize a .NET app with dotnet publish](publish-as-container.md) +- [Containerize a .NET app with dotnet publish](../containers/sdk-publish.md) - [.NET container images](container-images.md) - [Containerize an ASP.NET Core application](/aspnet/core/host-and-deploy/docker/building-net-docker-images) - [Azure services that support containers](https://azure.microsoft.com/overview/containers/) diff --git a/docs/core/docker/introduction.md b/docs/core/docker/introduction.md index 380b8838c9d18..db12434607f4b 100644 --- a/docs/core/docker/introduction.md +++ b/docs/core/docker/introduction.md @@ -21,7 +21,7 @@ Official .NET container images are published to the [Microsoft Artifact Registry ## Building container images -You can build a container image with a **Dockerfile** or rely on the [.NET SDK to produce an image](publish-as-container.md). For samples on building images, see [dotnet/dotnet-docker](https://github.com/dotnet/dotnet-docker/blob/main/samples/README.md) and [dotnet/sdk-container-builds](https://github.com/dotnet/sdk-container-builds). +You can build a container image with a **Dockerfile** or rely on the [.NET SDK to produce an image](../containers/sdk-publish.md). For samples on building images, see [dotnet/dotnet-docker](https://github.com/dotnet/dotnet-docker/blob/main/samples/README.md) and [dotnet/sdk-container-builds](https://github.com/dotnet/sdk-container-builds). The following example demonstrates building and running a container image in a few quick steps (supported with .NET 8 and .NET 7.0.300). diff --git a/docs/core/tools/dotnet-publish.md b/docs/core/tools/dotnet-publish.md index d67aac3ebcbe4..629fc014bf5ba 100644 --- a/docs/core/tools/dotnet-publish.md +++ b/docs/core/tools/dotnet-publish.md @@ -1,7 +1,7 @@ --- title: dotnet publish command description: The dotnet publish command publishes a .NET project or solution to a directory. -ms.date: 04/04/2024 +ms.date: 01/07/2025 --- # dotnet publish @@ -274,7 +274,7 @@ For more information, see the following resources: - [Publish .NET apps with the .NET CLI](../deploying/deploy-with-cli.md) - [Target frameworks](../../standard/frameworks.md) - [Runtime Identifier (RID) catalog](../rid-catalog.md) -- [Containerize a .NET app with dotnet publish](../docker/publish-as-container.md) +- [Containerize a .NET app with dotnet publish](../containers/sdk-publish.md) - [Working with macOS Catalina Notarization](../install/macos-notarization-issues.md) - [Directory structure of a published application](/aspnet/core/hosting/directory-structure) - [MSBuild command-line reference](/visualstudio/msbuild/msbuild-command-line-reference) diff --git a/docs/core/whats-new/dotnet-7.md b/docs/core/whats-new/dotnet-7.md index 45005f4f97eda..91bd02b3b38ab 100644 --- a/docs/core/whats-new/dotnet-7.md +++ b/docs/core/whats-new/dotnet-7.md @@ -109,7 +109,7 @@ For more information, see the [.NET 7 Preview 6](https://devblogs.microsoft.com/ ### Publish to a container -Containers are one of the easiest ways to distribute and run a wide variety of applications and services in the cloud. Container images are now a supported output type of the .NET SDK, and you can create containerized versions of your applications using [`dotnet publish`](../tools/dotnet-publish.md). For more information about the feature, see [Announcing built-in container support for the .NET SDK](https://devblogs.microsoft.com/dotnet/announcing-builtin-container-support-for-the-dotnet-sdk/). For a tutorial, see [Containerize a .NET app with dotnet publish](../docker/publish-as-container.md). +Containers are one of the easiest ways to distribute and run a wide variety of applications and services in the cloud. Container images are now a supported output type of the .NET SDK, and you can create containerized versions of your applications using [`dotnet publish`](../tools/dotnet-publish.md). For more information about the feature, see [Announcing built-in container support for the .NET SDK](https://devblogs.microsoft.com/dotnet/announcing-builtin-container-support-for-the-dotnet-sdk/). For a tutorial, see [Containerize a .NET app with dotnet publish](../containers/sdk-publish.md). ### Central package management diff --git a/docs/core/whats-new/dotnet-8/containers.md b/docs/core/whats-new/dotnet-8/containers.md index f80d02af7cb00..03af658176e97 100644 --- a/docs/core/whats-new/dotnet-8/containers.md +++ b/docs/core/whats-new/dotnet-8/containers.md @@ -102,7 +102,7 @@ These improvements also mean that more registries are supported: Harbor, Artifac > dotnet publish -r linux-x64 -p PublishProfile=DefaultContainer ``` -For more information containerizing .NET apps, see [Containerize a .NET app with dotnet publish](../../docker/publish-as-container.md). +For more information containerizing .NET apps, see [Containerize a .NET app with dotnet publish](../../containers/sdk-publish.md). ### Publish to tar.gz archive From 2bfa3559d841e9f5a13b53f622cffa80f700dbe9 Mon Sep 17 00:00:00 2001 From: David Pine Date: Tue, 7 Jan 2025 14:25:27 -0600 Subject: [PATCH 09/14] Correct remaining bad links --- docs/core/containers/publish-configuration.md | 2 +- docs/core/containers/sdk-publish.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core/containers/publish-configuration.md b/docs/core/containers/publish-configuration.md index 61e99e16beffb..05475b06a83a1 100644 --- a/docs/core/containers/publish-configuration.md +++ b/docs/core/containers/publish-configuration.md @@ -96,7 +96,7 @@ Starting with .NET 8, you can use the `ContainerFamily` MSBuild property to choo The preceding project configuration results in a final tag of `8.0-alpine` for a .NET 8-targeting app. -This field is free-form, and often can be used to select different operating system distributions, default package configurations, or any other _flavor_ of changes to a base image. This field is ignored when `ContainerBaseImage` is set. For more information, see [.NET container images](container-images.md). +This field is free-form, and often can be used to select different operating system distributions, default package configurations, or any other _flavor_ of changes to a base image. This field is ignored when `ContainerBaseImage` is set. For more information, see [.NET container images](../docker/container-images.md). ### `ContainerRuntimeIdentifier` diff --git a/docs/core/containers/sdk-publish.md b/docs/core/containers/sdk-publish.md index 840c63dc9d7b8..81d40d794b548 100644 --- a/docs/core/containers/sdk-publish.md +++ b/docs/core/containers/sdk-publish.md @@ -7,7 +7,7 @@ ms.topic: tutorial # Containerize a .NET app with dotnet publish -Containers have many features and benefits, such as being an immutable infrastructure, providing a portable architecture, and enabling scalability. The image can be used to create containers for your local development environment, private cloud, or public cloud. In this tutorial, you learn how to containerize a .NET application using the [dotnet publish](../tools/dotnet-publish.md) command without the use of a Dockerfile. Additionally, you explore how to configure the [container image](container-images.md) and execution, and how to clean up resources. +Containers have many features and benefits, such as being an immutable infrastructure, providing a portable architecture, and enabling scalability. The image can be used to create containers for your local development environment, private cloud, or public cloud. In this tutorial, you learn how to containerize a .NET application using the [dotnet publish](../tools/dotnet-publish.md) command without the use of a Dockerfile. Additionally, you explore how to configure the [container image](../docker/container-images.md) and execution, and how to clean up resources. > [!TIP] > If you're interested in using a _Dockerfile_ to containerize your .NET app, see [Tutorial: Containerize a .NET app](../docker/build-container.md). From 6ee7855e1cbe8da6b9cfdb314c15939a06450158 Mon Sep 17 00:00:00 2001 From: David Pine Date: Tue, 7 Jan 2025 14:27:35 -0600 Subject: [PATCH 10/14] Fix TOC --- docs/navigate/devops-testing/toc.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/navigate/devops-testing/toc.yml b/docs/navigate/devops-testing/toc.yml index 70bec53d2eec0..23ce5bafa6868 100644 --- a/docs/navigate/devops-testing/toc.yml +++ b/docs/navigate/devops-testing/toc.yml @@ -573,8 +573,6 @@ items: href: ../../core/docker/introduction.md - name: Containerize a .NET app href: ../../core/docker/build-container.md - - name: Publish app as container image - href: ../../core/docker/publish-as-container.md - name: .NET container images href: ../../core/docker/container-images.md - name: Container tools in Visual Studio From a2463e4cbf8b6823176f98cdd859f7f591f4d874 Mon Sep 17 00:00:00 2001 From: David Pine Date: Tue, 7 Jan 2025 14:32:15 -0600 Subject: [PATCH 11/14] Move images out of docker --- docs/navigate/devops-testing/toc.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/navigate/devops-testing/toc.yml b/docs/navigate/devops-testing/toc.yml index 23ce5bafa6868..3eaa89720a601 100644 --- a/docs/navigate/devops-testing/toc.yml +++ b/docs/navigate/devops-testing/toc.yml @@ -567,14 +567,14 @@ items: href: ../../core/containers/sdk-publish.md - name: .NET SDK publish configuration href: ../../core/containers/publish-configuration.md + - name: .NET container images + href: ../../core/docker/container-images.md - name: Docker items: - name: Introduction to .NET and Docker href: ../../core/docker/introduction.md - name: Containerize a .NET app href: ../../core/docker/build-container.md - - name: .NET container images - href: ../../core/docker/container-images.md - name: Container tools in Visual Studio href: /visualstudio/containers/overview?toc=/dotnet/navigate/devops-testing/toc.json&bc=/dotnet/breadcrumb/toc.json - name: .NET distribution packaging From c66e83b6e1b3da8f73f73d433ee018ac0e8cd28d Mon Sep 17 00:00:00 2001 From: David Pine Date: Tue, 7 Jan 2025 14:35:39 -0600 Subject: [PATCH 12/14] A few missed grammatical errors --- docs/core/containers/overview.md | 8 ++++---- docs/core/containers/publish-configuration.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/core/containers/overview.md b/docs/core/containers/overview.md index 7ed8b0be9b8b7..be8b082b694ff 100644 --- a/docs/core/containers/overview.md +++ b/docs/core/containers/overview.md @@ -32,10 +32,10 @@ The following information about how the base image inference process occurred is | `BaseImage` | The value of the base image chosen, but only if that base image is one of the Microsoft-produced images. If a user specifies any image other than the Microsoft-produced images on mcr.microsoft.com, this value is null. | `mcr.microsoft.com/dotnet/aspnet` | | `BaseImageTag` | The value of the tag chosen, but only if that tag is for one of the Microsoft-produced images. If a user specifies any image other than the Microsoft-produced images on mcr.microsoft.com, this value is null. | `8.0` | | `ContainerFamily` | The value of the `ContainerFamily` property if a user used the `ContainerFamily` feature to pick a 'flavor' of one of our base images. This is only set if the user picked or inferred one of the Microsoft-produced .NET images from mcr.microsoft.com | `jammy-chiseled` | -| `ProjectType` | What kind of project was containerized | `AspNetCore` or `Console` | -| `PublishMode` | How the application was packaged | `Aot`, `Trimmed`, `SelfContained`, or `FrameworkDependent` | -| `IsInvariant` | If the image chosen requires invariant globalization or the user opted into it manually | `true` | -| `TargetRuntime` | The RID that this application was published for | `linux-x64` | +| `ProjectType` | The kind of project that's being containerized. | `AspNetCore` or `Console` | +| `PublishMode` | How the application was packaged. | `Aot`, `Trimmed`, `SelfContained`, or `FrameworkDependent` | +| `IsInvariant` | If the image chosen requires invariant globalization or the user opted into it manually. | `true` | +| `TargetRuntime` | The RID that this application was published for. | `linux-x64` | ### Image creation telemetry diff --git a/docs/core/containers/publish-configuration.md b/docs/core/containers/publish-configuration.md index 05475b06a83a1..0c3bd713cc96f 100644 --- a/docs/core/containers/publish-configuration.md +++ b/docs/core/containers/publish-configuration.md @@ -397,7 +397,7 @@ dotnet publish -p ContainerUser=root Labels are often used to provide consistent metadata on container images. This package provides some default labels to encourage better maintainability of the generated images. -- `org.opencontainers.image.created` is set to the ISO 8601 format of the current UTC `DateTime`. +- `org.opencontainers.image.created` is set to the ISO 8601 format of the current value of . For more information, see [Implement conventional labels on top of existing label infrastructure](https://github.com/dotnet/sdk-container-builds/issues/96). From 7adf7ac6bc8029b60066301ab06dcfddacb4dfa4 Mon Sep 17 00:00:00 2001 From: David Pine Date: Tue, 7 Jan 2025 14:42:42 -0600 Subject: [PATCH 13/14] Update docs/navigate/devops-testing/toc.yml Co-authored-by: Chet Husk --- docs/navigate/devops-testing/toc.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/navigate/devops-testing/toc.yml b/docs/navigate/devops-testing/toc.yml index 3eaa89720a601..fd3d693eb78f5 100644 --- a/docs/navigate/devops-testing/toc.yml +++ b/docs/navigate/devops-testing/toc.yml @@ -563,9 +563,9 @@ items: items: - name: Overview href: ../../core/containers/overview.md - - name: .NET SDK publish container + - name: .NET SDK publishing tutorial href: ../../core/containers/sdk-publish.md - - name: .NET SDK publish configuration + - name: .NET SDK publishing reference href: ../../core/containers/publish-configuration.md - name: .NET container images href: ../../core/docker/container-images.md From e20188a21333f031bafd02108eaafaf9ad6a162d Mon Sep 17 00:00:00 2001 From: David Pine Date: Tue, 7 Jan 2025 14:43:38 -0600 Subject: [PATCH 14/14] Re-arrange content --- docs/core/containers/overview.md | 96 ++++++++++++++++---------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/docs/core/containers/overview.md b/docs/core/containers/overview.md index be8b082b694ff..d27bee304c389 100644 --- a/docs/core/containers/overview.md +++ b/docs/core/containers/overview.md @@ -9,54 +9,6 @@ ms.topic: overview While it's possible to [containerize .NET apps with a _Dockerfile_](../docker/build-container.md), there are compelling reasons for [containerizing apps directly with the .NET SDK](sdk-publish.md). This article provides an overview of the .NET SDK container creation feature, with details related to telemetry, publishing considerations, build properties, and authentication to container registries. -## Telemetry - -When you publish a .NET app as a container, the .NET SDK container tooling collects and sends usage telemetry about how the tools are used. The collected data is in addition to the [telemetry sent by the .NET CLI](../tools/telemetry.md), but uses the same mechanisms and, importantly, adheres to the same [opt-out controls](../tools/telemetry.md#how-to-opt-out). - -The telemetry gathered is intended to be general in nature and not leak any personal informationβ€”the intended purpose is to help measure: - -- Usage of the .NET SDK containerization feature overall. -- Success and failure rates, along with general information about what kinds of failures happen most frequently. -- Usage of specific features of the tech, like publishing to various registry kinds, or how the publish was invoked. - -To opt-out of telemetry, set the `DOTNET_CLI_TELEMETRY_OPTOUT` environment variable to `true`. For more information, see [.NET CLI telemetry](../tools/telemetry.md). - -### Inference telemetry - -The following information about how the base image inference process occurred is logged: - -| Date point | Explanation | Sample value | -|--|--|--| -| `InferencePerformed` | If users are manually specifying base images vs making use of inference. | `true` | -| `TargetFramework` | The `TargetFramework` chosen when doing base image inference. | `net8.0` | -| `BaseImage` | The value of the base image chosen, but only if that base image is one of the Microsoft-produced images. If a user specifies any image other than the Microsoft-produced images on mcr.microsoft.com, this value is null. | `mcr.microsoft.com/dotnet/aspnet` | -| `BaseImageTag` | The value of the tag chosen, but only if that tag is for one of the Microsoft-produced images. If a user specifies any image other than the Microsoft-produced images on mcr.microsoft.com, this value is null. | `8.0` | -| `ContainerFamily` | The value of the `ContainerFamily` property if a user used the `ContainerFamily` feature to pick a 'flavor' of one of our base images. This is only set if the user picked or inferred one of the Microsoft-produced .NET images from mcr.microsoft.com | `jammy-chiseled` | -| `ProjectType` | The kind of project that's being containerized. | `AspNetCore` or `Console` | -| `PublishMode` | How the application was packaged. | `Aot`, `Trimmed`, `SelfContained`, or `FrameworkDependent` | -| `IsInvariant` | If the image chosen requires invariant globalization or the user opted into it manually. | `true` | -| `TargetRuntime` | The RID that this application was published for. | `linux-x64` | - -### Image creation telemetry - -The following information about how the container creation and publishing process occurred is logged: - -| Date point | Explanation | Sample value | -|--|--|--| -| `RemotePullType` | If the base image came from a remote registry, what kind of registry was it? | Azure, AWS, Google, GitHub, DockerHub, MRC, or Other | -| `LocalPullType` | If the base image came from a local source, like a container daemon or a tarball. | Docker, Podman, Tarball | -| `RemotePushType` | If the image was pushed to a remote registry, what kind of registry was it? | Azure, AWS, Google, GitHub, DockerHub, MRC, or Other | -| `LocalPushType` | If the image was pushed to a local destination, what was it? | Docker, Podman, Tarball | - -In addition, if various kinds of errors occur during the process that data is collected about what kind of error it was: - -| Date point | Explanation | Sample value | -|--|--|--| -| `Error` | The kind of error that occurred | `unknown_repository`, `credential_failure`, `rid_mismatch`, `local_load`. | -| `Direction` | If the error is a `credential_failure`, was it to the push or pull registry? | `push` | -| Target RID | If the error was a `rid_mismatch`, what RID was requested | `linux-x64` | -| Available RIDs | If the error was a `rid_mismatch`, what RIDs did the base image support? | `linux-x64,linux-arm64` | - ## Publishing project considerations Now that you have a .NET app, you can publish it as a container. Before doing so, there are several important considerations to keep in mind. Prior to .NET SDK version 8.0.200, you needed the [πŸ“¦ Microsoft.NET.Build.Containers](https://www.nuget.org/packages/Microsoft.NET.Build.Containers) NuGet package. This package isn't required for .NET SDK version 8.0.200 and later, as the container support is included by default. @@ -201,6 +153,54 @@ DOTNET_CONTAINER_INSECURE_REGISTRIES=localhost:5000,registry.mycorp.com dotnet p --- +## Telemetry + +When you publish a .NET app as a container, the .NET SDK container tooling collects and sends usage telemetry about how the tools are used. The collected data is in addition to the [telemetry sent by the .NET CLI](../tools/telemetry.md), but uses the same mechanisms and, importantly, adheres to the same [opt-out controls](../tools/telemetry.md#how-to-opt-out). + +The telemetry gathered is intended to be general in nature and not leak any personal informationβ€”the intended purpose is to help measure: + +- Usage of the .NET SDK containerization feature overall. +- Success and failure rates, along with general information about what kinds of failures happen most frequently. +- Usage of specific features of the tech, like publishing to various registry kinds, or how the publish was invoked. + +To opt-out of telemetry, set the `DOTNET_CLI_TELEMETRY_OPTOUT` environment variable to `true`. For more information, see [.NET CLI telemetry](../tools/telemetry.md). + +### Inference telemetry + +The following information about how the base image inference process occurred is logged: + +| Date point | Explanation | Sample value | +|--|--|--| +| `InferencePerformed` | If users are manually specifying base images vs making use of inference. | `true` | +| `TargetFramework` | The `TargetFramework` chosen when doing base image inference. | `net8.0` | +| `BaseImage` | The value of the base image chosen, but only if that base image is one of the Microsoft-produced images. If a user specifies any image other than the Microsoft-produced images on mcr.microsoft.com, this value is null. | `mcr.microsoft.com/dotnet/aspnet` | +| `BaseImageTag` | The value of the tag chosen, but only if that tag is for one of the Microsoft-produced images. If a user specifies any image other than the Microsoft-produced images on mcr.microsoft.com, this value is null. | `8.0` | +| `ContainerFamily` | The value of the `ContainerFamily` property if a user used the `ContainerFamily` feature to pick a 'flavor' of one of our base images. This is only set if the user picked or inferred one of the Microsoft-produced .NET images from mcr.microsoft.com | `jammy-chiseled` | +| `ProjectType` | The kind of project that's being containerized. | `AspNetCore` or `Console` | +| `PublishMode` | How the application was packaged. | `Aot`, `Trimmed`, `SelfContained`, or `FrameworkDependent` | +| `IsInvariant` | If the image chosen requires invariant globalization or the user opted into it manually. | `true` | +| `TargetRuntime` | The RID that this application was published for. | `linux-x64` | + +### Image creation telemetry + +The following information about how the container creation and publishing process occurred is logged: + +| Date point | Explanation | Sample value | +|--|--|--| +| `RemotePullType` | If the base image came from a remote registry, what kind of registry was it? | Azure, AWS, Google, GitHub, DockerHub, MRC, or Other | +| `LocalPullType` | If the base image came from a local source, like a container daemon or a tarball. | Docker, Podman, Tarball | +| `RemotePushType` | If the image was pushed to a remote registry, what kind of registry was it? | Azure, AWS, Google, GitHub, DockerHub, MRC, or Other | +| `LocalPushType` | If the image was pushed to a local destination, what was it? | Docker, Podman, Tarball | + +In addition, if various kinds of errors occur during the process that data is collected about what kind of error it was: + +| Date point | Explanation | Sample value | +|--|--|--| +| `Error` | The kind of error that occurred | `unknown_repository`, `credential_failure`, `rid_mismatch`, `local_load`. | +| `Direction` | If the error is a `credential_failure`, was it to the push or pull registry? | `push` | +| Target RID | If the error was a `rid_mismatch`, what RID was requested | `linux-x64` | +| Available RIDs | If the error was a `rid_mismatch`, what RIDs did the base image support? | `linux-x64,linux-arm64` | + ## See also - [Publish .NET apps as containers](sdk-publish.md)