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/.openpublishing.redirection.machine-learning.json b/.openpublishing.redirection.machine-learning.json index 3746904e59d61..0f132936c8860 100644 --- a/.openpublishing.redirection.machine-learning.json +++ b/.openpublishing.redirection.machine-learning.json @@ -6,8 +6,7 @@ }, { "source_path_from_root": "/docs/machine-learning/basic-concepts-model-training-in-mldotnet.md", - "redirect_url": "/dotnet/machine-learning/how-does-mldotnet-work", - "redirect_document_id": true + "redirect_url": "/dotnet/machine-learning/mldotnet-api" }, { "source_path_from_root": "/docs/machine-learning/how-does-mldotnet-work.md", @@ -79,11 +78,11 @@ }, { "source_path_from_root": "/docs/machine-learning/resources/basics.md", - "redirect_url": "/dotnet/machine-learning/how-does-mldotnet-work" + "redirect_url": "/dotnet/machine-learning/mldotnet-api" }, { "source_path_from_root": "/docs/machine-learning/resources/what-is-mldotnet.md", - "redirect_url": "/dotnet/machine-learning/how-does-mldotnet-work" + "redirect_url": "/dotnet/machine-learning/mldotnet-api" }, { "source_path_from_root": "/docs/machine-learning/resources/index.md", @@ -107,7 +106,7 @@ }, { "source_path_from_root": "/docs/machine-learning/what-is-machine-learning.md", - "redirect_url": "/dotnet/machine-learning/how-does-mldotnet-work" + "redirect_url": "/dotnet/machine-learning/mldotnet-api" } ] } diff --git a/.openpublishing.redirection.standard.json b/.openpublishing.redirection.standard.json index 25dc618ea2cfd..b2079ad7ab323 100644 --- a/.openpublishing.redirection.standard.json +++ b/.openpublishing.redirection.standard.json @@ -736,7 +736,7 @@ }, { "source_path_from_root": "/docs/standard/serialization/system-text-json-supported-collection-types.md", - "redirect_url": "/dotnet/standard/serialization/system-text-json/supported-collection-types", + "redirect_url": "/dotnet/standard/serialization/system-text-json/supported-types", "redirect_document_id": true }, { diff --git a/docs/azure/includes/dotnet-all.md b/docs/azure/includes/dotnet-all.md index 4789c67b20b05..cb756e05f2d2b 100644 --- a/docs/azure/includes/dotnet-all.md +++ b/docs/azure/includes/dotnet-all.md @@ -462,7 +462,7 @@ | Speech Xamarin iOS | NuGet [1.25.0](https://www.nuget.org/packages/Microsoft.CognitiveServices.Speech.Xamarin.iOS/1.25.0) | | | | Spell Check | NuGet [4.1.0-preview.1](https://www.nuget.org/packages/Microsoft.Azure.CognitiveServices.Language.SpellCheck/4.1.0-preview.1) | | GitHub [4.1.0-preview.1](https://github.com/Azure/azure-sdk-for-net/tree/Microsoft.Azure.CognitiveServices.Language.SpellCheck_4.1.0-preview.1/sdk/cognitiveservices/Language.SpellCheck) | | Spring Cloud Client | NuGet [2.0.0-preview.3](https://www.nuget.org/packages/Microsoft.Azure.SpringCloud.Client/2.0.0-preview.3) | | | -| Storage - Files Data Lake | NuGet [2.0.1-alpha.1](https://www.nuget.org/packages/Microsoft.Azure.DataLake.Store/2.0.1-alpha.1) | [docs](https://learn.microsoft.com/dotnet/api/overview/azure/data-lake-store) | GitHub [2.0.1-alpha.1](https://github.com/Azure/azure-data-lake-store-net/tree/1.2.3-alpha) | +| Storage - Files Data Lake | NuGet [2.0.1](https://www.nuget.org/packages/Microsoft.Azure.DataLake.Store/2.0.1) | [docs](https://learn.microsoft.com/dotnet/api/overview/azure/data-lake-store) | GitHub [2.0.1](https://github.com/Azure/azure-data-lake-store-net/tree/1.2.3-alpha) | | Synapse Analytics | NuGet [0.1.0-preview](https://www.nuget.org/packages/Microsoft.Azure.Synapse/0.1.0-preview) | | GitHub [0.1.0-preview](https://github.com/Azure/azure-sdk-for-net/tree/Microsoft.Azure.Synapse_0.1.0-preview/sdk/synapse/Microsoft.Azure.Synapse/) | | Tables | NuGet [2.1.2](https://www.nuget.org/packages/Microsoft.Azure.CosmosDB.Table/2.1.2) | | | | Video Search | NuGet [2.1.0-preview.1](https://www.nuget.org/packages/Microsoft.Azure.CognitiveServices.Search.BingVideoSearch/2.1.0-preview.1) | | GitHub [2.1.0-preview.1](https://github.com/Azure/azure-sdk-for-net/tree/Microsoft.Azure.CognitiveServices.Search.BingVideoSearch_2.1.0-preview.1/sdk/cognitiveservices/Search.BingVideoSearch) | 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 new file mode 100644 index 0000000000000..d27bee304c389 --- /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. + +## 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 +``` + +--- + +## 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) +- [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 62% rename from docs/core/docker/publish-as-container.md rename to docs/core/containers/publish-configuration.md index 37446fc24131e..0c3bd713cc96f 100644 --- a/docs/core/docker/publish-as-container.md +++ b/docs/core/containers/publish-configuration.md @@ -1,146 +1,26 @@ --- -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 -ms.topic: tutorial +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 --- -# 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 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. -- [Docker Community Edition](https://www.docker.com/products/docker-desktop) - -In addition to these prerequisites, it's recommended that you're familiar with [Worker Services in .NET](../extensions/workers.md). - -## 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 like the following: - -```Directory -πŸ“ sample-directory - β””β”€β”€πŸ“‚ Worker - β”œβ”€β”€appsettings.Development.json - β”œβ”€β”€appsettings.json - β”œβ”€β”€DotNet.ContainerImage.csproj - β”œβ”€β”€Program.cs - β”œβ”€β”€Worker.cs - β””β”€β”€πŸ“‚ 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 -Building... -info: DotNet.ContainerImage.Worker[0] - Worker running at: 10/18/2022 08:56:00 -05: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: 10/18/2022 08:56:01 -05: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 -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 - -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. - -> [!IMPORTANT] -> By default, the `IsPublishable` property is set to `true` for `console`, `webapp`, and `worker` templates. - -To enable SDK container support, set the `EnableSdkContainerSupport` property to `true` in your project file. - -```xml - - true - true - -``` - -## 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/8.0/Worker/DotNet.ContainerImage.csproj" highlight="8"::: - -For more information, 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 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: -> -> ```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: - -```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 -``` - -This command compiles your worker app to the *publish* folder and pushes the container to your local docker registry. +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 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: @@ -150,7 +30,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 @@ -196,7 +76,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. @@ -216,11 +96,11 @@ 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` -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. @@ -244,13 +124,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). @@ -296,7 +176,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"' @@ -307,18 +187,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 @@ -346,7 +226,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: @@ -384,6 +264,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. @@ -401,7 +284,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: @@ -467,7 +350,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). @@ -476,14 +359,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 @@ -514,39 +397,11 @@ 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). -## 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..81d40d794b548 --- /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](../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). + +## 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`](publish-configuration.md#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`](publish-configuration.md#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](publish-configuration.md#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](../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/snippets/8.0/Worker/DotNet.ContainerImage.csproj b/docs/core/containers/snippets/Worker/DotNet.ContainerImage.csproj similarity index 90% rename from docs/core/docker/snippets/8.0/Worker/DotNet.ContainerImage.csproj rename to docs/core/containers/snippets/Worker/DotNet.ContainerImage.csproj index 49a4f590f520d..2410a64fb67d5 100644 --- a/docs/core/docker/snippets/8.0/Worker/DotNet.ContainerImage.csproj +++ b/docs/core/containers/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/containers/snippets/Worker/Program.cs similarity index 100% rename from docs/core/docker/snippets/8.0/Worker/Program.cs rename to docs/core/containers/snippets/Worker/Program.cs diff --git a/docs/core/docker/snippets/8.0/Worker/Properties/launchSettings.json b/docs/core/containers/snippets/Worker/Properties/launchSettings.json similarity index 100% rename from docs/core/docker/snippets/8.0/Worker/Properties/launchSettings.json rename to docs/core/containers/snippets/Worker/Properties/launchSettings.json diff --git a/docs/core/docker/snippets/8.0/Worker/Worker.cs b/docs/core/containers/snippets/Worker/Worker.cs similarity index 100% rename from docs/core/docker/snippets/8.0/Worker/Worker.cs rename to docs/core/containers/snippets/Worker/Worker.cs diff --git a/docs/core/docker/snippets/8.0/Worker/appsettings.Development.json b/docs/core/containers/snippets/Worker/appsettings.Development.json similarity index 100% rename from docs/core/docker/snippets/8.0/Worker/appsettings.Development.json rename to docs/core/containers/snippets/Worker/appsettings.Development.json diff --git a/docs/core/docker/snippets/8.0/Worker/appsettings.json b/docs/core/containers/snippets/Worker/appsettings.json similarity index 100% rename from docs/core/docker/snippets/8.0/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 63528a76b398f..7cff5e7ee2c39 100644 --- a/docs/core/docker/build-container.md +++ b/docs/core/docker/build-container.md @@ -1,9 +1,10 @@ --- 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 #Customer intent: As a developer, I want to containerize my .NET app so that I can deploy it to the cloud. --- @@ -20,7 +21,10 @@ 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. + +> [!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. @@ -32,11 +36,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 +61,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 +69,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 +78,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 +88,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 +110,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](../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/_: #### [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 +165,102 @@ 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. + +:::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. -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-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 + +> [!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 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 +281,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 +297,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 +315,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 `FROM` command specifies the base image and tag to use. The `WORKDIR` command changes the **current directory** inside of the container to _App_. -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 `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 will automatically stop. +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 +365,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 +404,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 +479,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) @@ -380,6 +508,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. @@ -397,6 +528,9 @@ root@9f8de8fbd4a8:/App# exit exit ``` +> [!NOTE] +> This example only works on Linux containers. Windows containers don't have `bash`. + --- ## Essential commands @@ -433,13 +567,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] @@ -447,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/docker/snippets/8.0/App/Dockerfile b/docs/core/docker/snippets/8.0/App/Dockerfile index 01b0a0a805b8a..c1678c5d0801a 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 @@ -6,10 +6,10 @@ COPY . ./ # Restore as distinct layers RUN dotnet restore # Build and publish a release -RUN dotnet publish -c Release -o out +RUN dotnet publish -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..25fe2535d57b6 --- /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 -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)); +} diff --git a/docs/core/extensions/artificial-intelligence.md b/docs/core/extensions/artificial-intelligence.md index d872a9b14f55e..e346df29d84ca 100644 --- a/docs/core/extensions/artificial-intelligence.md +++ b/docs/core/extensions/artificial-intelligence.md @@ -3,7 +3,7 @@ title: Artificial Intelligence in .NET (Preview) description: Learn how to use the Microsoft.Extensions.AI library to integrate and interact with various AI services in your .NET applications. author: IEvangelist ms.author: dapine -ms.date: 12/17/2024 +ms.date: 01/06/2025 ms.collection: ce-skilling-ai-copilot --- @@ -210,7 +210,7 @@ For scenarios where the developer would like to specify delegating implementatio implementations will typically be provided to an application via [dependency injection (DI)](dependency-injection.md). In this example, an is added into the DI container, as is an `IChatClient`. The registration for the `IChatClient` employs a builder that creates a pipeline containing a caching client (which will then use an `IDistributedCache` retrieved from DI) and the sample client. The injected `IChatClient` can be retrieved and used elsewhere in the app. -::code language="csharp" source="snippets/ai/ConsoleAI.DependencyInjection/Program.cs"::: +:::code language="csharp" source="snippets/ai/ConsoleAI.DependencyInjection/Program.cs"::: The preceding example depends on the following NuGet packages: @@ -251,7 +251,7 @@ You can find actual concrete implementations in the following packages: The primary operation performed with an is embedding generation, which is accomplished with its method. -::code language="csharp" source="snippets/ai/ConsoleAI.CreateEmbeddings/Program.cs"::: +:::code language="csharp" source="snippets/ai/ConsoleAI.CreateEmbeddings/Program.cs"::: #### Custom `IEmbeddingGenerator` middleware diff --git a/docs/core/project-sdk/msbuild-props.md b/docs/core/project-sdk/msbuild-props.md index 8dd0fa61480d1..2cb309e3a6b0e 100644 --- a/docs/core/project-sdk/msbuild-props.md +++ b/docs/core/project-sdk/msbuild-props.md @@ -1783,7 +1783,16 @@ In addition to the standard [MSBuild item attributes](/visualstudio/msbuild/item ### CopyToPublishDirectory -The `CopyToPublishDirectory` metadata on an MSBuild item controls when the item is copied to the publish directory. Allowable values are `PreserveNewest`, which only copies the item if it has changed, `Always`, which always copies the item, and `Never`, which never copies the item. From a performance standpoint, `PreserveNewest` is preferable because it enables an incremental build. +The `CopyToPublishDirectory` metadata on an MSBuild item controls when the item is copied to the publish directory. The following table shows the allowable values. + +| Value | Description | +| ------ | ------------ | +| `PreserveNewest` | Only copies the item if it has changed in the source location. | +| `IfDifferent` | Only copies the item if it has changed either in the source or target location. This setting is helpful for situations where you need to reset changes that occur after publishing. | +| `Always` | Always copies the item. | +| `Never` | Never copies the item. | + +From a performance standpoint, `PreserveNewest` is preferable because it enables an incremental build. Avoid using `Always` and use `IfDifferent` instead, which avoids I/O writes with no effect. ```xml diff --git a/docs/core/testing/mstest-analyzers/design-rules.md b/docs/core/testing/mstest-analyzers/design-rules.md index 69fece1ee2522..f24d93893c379 100644 --- a/docs/core/testing/mstest-analyzers/design-rules.md +++ b/docs/core/testing/mstest-analyzers/design-rules.md @@ -13,7 +13,7 @@ Design rules will help you create and maintain test suites that adhere to proper Identifier | Name | Description -----------|------|------------ [MSTEST0004](mstest0004.md) | PublicTypeShouldBeTestClassAnalyzer | It's considered a good practice to have only test classes marked public in a test project. -[MSTEST0006](mstest0006.md) | AvoidExpectedExceptionAttributeAnalyzer | Prefer `Assert.ThrowsException` or `Assert.ThrowsExceptionAsync` over `[ExpectedException]` as it ensures that only the expected call throws the expected exception. The assert APIs also provide more flexibility and allow you to assert extra properties of the exception. +[MSTEST0006](mstest0006.md) | AvoidExpectedExceptionAttributeAnalyzer | Prefer `Assert.ThrowsExactly` or `Assert.ThrowsExactlyAsync` over `[ExpectedException]` as it ensures that only the expected call throws the expected exception. The assert APIs also provide more flexibility and allow you to assert extra properties of the exception. [MSTEST0015](mstest0015.md) | TestMethodShouldNotBeIgnored | Test methods should not be ignored (marked with `[Ignore]`). [MSTEST0016](mstest0016.md) | TestClassShouldHaveTestMethod | Test class should have at least one test method or be 'static' with method(s) marked by `[AssemblyInitialization]` and/or `[AssemblyCleanup]`. [MSTEST0019](mstest0019.md) | PreferTestInitializeOverConstructorAnalyzer | Prefer TestInitialize methods over constructors diff --git a/docs/core/testing/mstest-analyzers/mstest0006.md b/docs/core/testing/mstest-analyzers/mstest0006.md index cf2497a8ae463..d1ff170c79984 100644 --- a/docs/core/testing/mstest-analyzers/mstest0006.md +++ b/docs/core/testing/mstest-analyzers/mstest0006.md @@ -33,7 +33,7 @@ A method is marked with the `[ExpectedException]` attribute. ## Rule description -Prefer `Assert.ThrowsException` or `Assert.ThrowsExceptionAsync` over the `[ExpectedException]` attribute as it ensures that only the expected line of code throws the expected exception, instead of acting on the whole body of the test. The assert APIs also provide more flexibility and allow you to assert extra properties of the exception. +Prefer `Assert.ThrowsException` or `Assert.ThrowsExceptionAsync` (or `Assert.ThrowsExactly`/`Assert.Throws` or `Assert.ThrowsExactlyAsync`/`Assert.ThrowsAsync` if using MSTest 3.8 and later) over the `[ExpectedException]` attribute as it ensures that only the expected line of code throws the expected exception, instead of acting on the whole body of the test. The assert APIs also provide more flexibility and allow you to assert extra properties of the exception. ```csharp [TestClass] @@ -59,7 +59,7 @@ public class TestClass ## How to fix violations -Replace the usage of the `[ExpectedException]` attribute by a call to `Assert.ThrowsException` or `Assert.ThrowsExceptionAsync`. +Replace the usage of the `[ExpectedException]` attribute by a call to `Assert.ThrowsException` or `Assert.ThrowsExceptionAsync` (or `Assert.ThrowsExactly`/`Assert.Throws` or `Assert.ThrowsExactlyAsync`/`Assert.ThrowsAsync` if using MSTest 3.8 and later). ```csharp [TestClass] @@ -77,7 +77,7 @@ public class TestClass person.SetAge(-1); // Act - Assert.ThrowsException(() => person.GrowOlder()); + Assert.ThrowsExactly(() => person.GrowOlder()); } } ``` diff --git a/docs/core/testing/mstest-analyzers/mstest0031.md b/docs/core/testing/mstest-analyzers/mstest0031.md index 612cfd4ecd302..06605961fbebb 100644 --- a/docs/core/testing/mstest-analyzers/mstest0031.md +++ b/docs/core/testing/mstest-analyzers/mstest0031.md @@ -11,7 +11,7 @@ helpviewer_keywords: author: engyebrahim ms.author: enjieid --- -# MSTEST0031: `System.ComponentModel.DescriptionAttribute` has no effect on test methods. +# MSTEST0031: `System.ComponentModel.DescriptionAttribute` has no effect on test methods | Property | Value | |-------------------------------------|-----------------------------------------------------------------------------| diff --git a/docs/core/testing/mstest-analyzers/mstest0032.md b/docs/core/testing/mstest-analyzers/mstest0032.md index 5856018759989..687c7a2e3409e 100644 --- a/docs/core/testing/mstest-analyzers/mstest0032.md +++ b/docs/core/testing/mstest-analyzers/mstest0032.md @@ -11,7 +11,7 @@ helpviewer_keywords: author: engyebrahim ms.author: enjieid --- -# MSTEST0032: Review or remove the assertion as its condition is known to be always true. +# MSTEST0032: Review or remove the assertion as its condition is known to be always true | Property | Value | |-------------------------------------|-----------------------------------------------------------------------------| diff --git a/docs/core/testing/unit-testing-mstest-writing-tests-attributes.md b/docs/core/testing/unit-testing-mstest-writing-tests-attributes.md index 35f1e562d2713..09dc93f3acfee 100644 --- a/docs/core/testing/unit-testing-mstest-writing-tests-attributes.md +++ b/docs/core/testing/unit-testing-mstest-writing-tests-attributes.md @@ -390,7 +390,7 @@ public class UnitTest1 The MSTest framework introduced for marking a test method to expect an exception of a specific type. The test will pass if the expected exception is thrown and the exception message matches the expected message. > [!WARNING] -> This attribute exists for backward compatibility and is not recommended for new tests. Instead, use the `Assert.ThrowsException` method. +> This attribute exists for backward compatibility and is not recommended for new tests. Instead, use the `Assert.ThrowsException` (or `Assert.ThrowsExactly` if using MSTest 3.8 and later) method. ## Metadata attributes 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/tutorials/media/debugging-with-visual-studio-code/select-terminal-net6.png b/docs/core/tutorials/media/debugging-with-visual-studio-code/select-terminal-net6.png deleted file mode 100644 index cb05ea3b68b59..0000000000000 Binary files a/docs/core/tutorials/media/debugging-with-visual-studio-code/select-terminal-net6.png and /dev/null differ diff --git a/docs/core/tutorials/media/library-with-visual-studio/vb/library-project-properties-net6.png b/docs/core/tutorials/media/library-with-visual-studio/vb/library-project-properties-net6.png deleted file mode 100644 index 65e79a4387180..0000000000000 Binary files a/docs/core/tutorials/media/library-with-visual-studio/vb/library-project-properties-net6.png and /dev/null differ diff --git a/docs/core/tutorials/media/library-with-visual-studio/vb/library-project-properties-net7.png b/docs/core/tutorials/media/library-with-visual-studio/vb/library-project-properties-net7.png deleted file mode 100644 index 838a041bf115f..0000000000000 Binary files a/docs/core/tutorials/media/library-with-visual-studio/vb/library-project-properties-net7.png and /dev/null differ diff --git a/docs/core/tutorials/media/publishing-with-visual-studio-code/published-files-output-net6.png b/docs/core/tutorials/media/publishing-with-visual-studio-code/published-files-output-net6.png deleted file mode 100644 index a20912533a661..0000000000000 Binary files a/docs/core/tutorials/media/publishing-with-visual-studio-code/published-files-output-net6.png and /dev/null differ diff --git a/docs/core/tutorials/media/publishing-with-visual-studio-code/published-files-output-net7.png b/docs/core/tutorials/media/publishing-with-visual-studio-code/published-files-output-net7.png deleted file mode 100644 index c53081a1b5516..0000000000000 Binary files a/docs/core/tutorials/media/publishing-with-visual-studio-code/published-files-output-net7.png and /dev/null differ diff --git a/docs/core/tutorials/media/publishing-with-visual-studio/publish-page-loc-tab-net6.png b/docs/core/tutorials/media/publishing-with-visual-studio/publish-page-loc-tab-net6.png deleted file mode 100644 index c73dec96d12aa..0000000000000 Binary files a/docs/core/tutorials/media/publishing-with-visual-studio/publish-page-loc-tab-net6.png and /dev/null differ diff --git a/docs/core/tutorials/media/publishing-with-visual-studio/publish-page-loc-tab-net7.png b/docs/core/tutorials/media/publishing-with-visual-studio/publish-page-loc-tab-net7.png deleted file mode 100644 index 9ce5a9d6ab6cd..0000000000000 Binary files a/docs/core/tutorials/media/publishing-with-visual-studio/publish-page-loc-tab-net7.png and /dev/null differ diff --git a/docs/core/tutorials/media/publishing-with-visual-studio/publish-page-net6.png b/docs/core/tutorials/media/publishing-with-visual-studio/publish-page-net6.png deleted file mode 100644 index 10f9b52f517bc..0000000000000 Binary files a/docs/core/tutorials/media/publishing-with-visual-studio/publish-page-net6.png and /dev/null differ diff --git a/docs/core/tutorials/media/publishing-with-visual-studio/publish-page-net7.png b/docs/core/tutorials/media/publishing-with-visual-studio/publish-page-net7.png deleted file mode 100644 index dc993fb13b061..0000000000000 Binary files a/docs/core/tutorials/media/publishing-with-visual-studio/publish-page-net7.png and /dev/null differ diff --git a/docs/core/tutorials/media/publishing-with-visual-studio/published-files-output-net6.png b/docs/core/tutorials/media/publishing-with-visual-studio/published-files-output-net6.png deleted file mode 100644 index 086c7290e3437..0000000000000 Binary files a/docs/core/tutorials/media/publishing-with-visual-studio/published-files-output-net6.png and /dev/null differ diff --git a/docs/core/tutorials/media/publishing-with-visual-studio/published-files-output-net7.png b/docs/core/tutorials/media/publishing-with-visual-studio/published-files-output-net7.png deleted file mode 100644 index 1311379b718c0..0000000000000 Binary files a/docs/core/tutorials/media/publishing-with-visual-studio/published-files-output-net7.png and /dev/null differ diff --git a/docs/core/tutorials/media/with-visual-studio-code/dotnet-run-command.png b/docs/core/tutorials/media/with-visual-studio-code/dotnet-run-command.png deleted file mode 100644 index ac305a8a2c7b9..0000000000000 Binary files a/docs/core/tutorials/media/with-visual-studio-code/dotnet-run-command.png and /dev/null differ diff --git a/docs/core/tutorials/testing-library-with-visual-studio-code.md b/docs/core/tutorials/testing-library-with-visual-studio-code.md index dc5f017791c2d..b919c28623230 100644 --- a/docs/core/tutorials/testing-library-with-visual-studio-code.md +++ b/docs/core/tutorials/testing-library-with-visual-studio-code.md @@ -67,7 +67,7 @@ The most common tests call members of the method in a test method to indicate the type of exception it's expected to throw. The test fails if the specified exception isn't thrown. +You can also use the (or `Assert.Throws` and `Assert.ThrowsExactly` if using MSTest 3.8 and later) method in a test method to indicate the type of exception it's expected to throw. The test fails if the specified exception isn't thrown. In testing the `StringLibrary.StartsWithUpper` method, you want to provide a number of strings that begin with an uppercase character. You expect the method to return `true` in these cases, so you can call the method. Similarly, you want to provide a number of strings that begin with something other than an uppercase character. You expect the method to return `false` in these cases, so you can call the method. diff --git a/docs/core/tutorials/testing-library-with-visual-studio.md b/docs/core/tutorials/testing-library-with-visual-studio.md index c5254af5f5a2b..c55e38684d86e 100644 --- a/docs/core/tutorials/testing-library-with-visual-studio.md +++ b/docs/core/tutorials/testing-library-with-visual-studio.md @@ -95,7 +95,7 @@ The most common tests call members of the method in a test method to indicate the type of exception it's expected to throw. The test fails if the specified exception isn't thrown. +You can also use the (or `Assert.Throws` and `Assert.ThrowsExactly` if using MSTest 3.8 and later) method in a test method to indicate the type of exception it's expected to throw. The test fails if the specified exception isn't thrown. In testing the `StringLibrary.StartsWithUpper` method, you want to provide a number of strings that begin with an uppercase character. You expect the method to return `true` in these cases, so you can call the method. Similarly, you want to provide a number of strings that begin with something other than an uppercase character. You expect the method to return `false` in these cases, so you can call the method. 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 diff --git a/docs/framework/data/adonet/sql-server-connection-pooling.md b/docs/framework/data/adonet/sql-server-connection-pooling.md index 9de1ed418b6e3..bfa72947dfc27 100644 --- a/docs/framework/data/adonet/sql-server-connection-pooling.md +++ b/docs/framework/data/adonet/sql-server-connection-pooling.md @@ -12,6 +12,9 @@ Connecting to a database server typically consists of several time-consuming ste In practice, most applications use only one or a few different configurations for connections. This means that during application execution, many identical connections will be repeatedly opened and closed. To minimize the cost of opening connections, ADO.NET uses an optimization technique called *connection pooling*. +> [!NOTE] +> Connection pooling in ADO.NET works consistently across all SQL Server-based environments, including Azure SQL Database, Azure SQL Managed Instance, and SQL Server (on-premises or in virtual machines (VMs)). The pooling mechanism is entirely client-side and functions identically across these platforms. However, service-specific factors can influence pooling efficiency: Azure SQL Database enforces connection limits based on the selected service tier (e.g., Basic, Standard, Premium), while Azure SQL Managed Instance ties connection limits to the instance's allocated resources, such as vCores and memory. In contrast, SQL Server on VMs has no enforced limits beyond hardware and licensing constraints, offering the most flexibility. + Connection pooling reduces the number of times that new connections must be opened. The *pooler* maintains ownership of the physical connection. It manages connections by keeping alive a set of active connections for each given connection configuration. Whenever a user calls `Open` on a connection, the pooler looks for an available connection in the pool. If a pooled connection is available, it returns it to the caller instead of opening a new connection. When the application calls `Close` on the connection, the pooler returns it to the pooled set of active connections instead of closing it. Once the connection is returned to the pool, it is ready to be reused on the next `Open` call. Only connections with the same configuration can be pooled. ADO.NET keeps several pools at the same time, one for each configuration. Connections are separated into pools by connection string, and by Windows identity when integrated security is used. Connections are also pooled based on whether they are enlisted in a transaction. When using , the instance affects the connection pool. Different instances of will use different connection pools, even if the user ID and password are the same. diff --git a/docs/framework/toc.yml b/docs/framework/toc.yml index 90306a070a1a0..3cb088b81f694 100644 --- a/docs/framework/toc.yml +++ b/docs/framework/toc.yml @@ -27,6 +27,8 @@ items: href: install/on-windows-8-1.md - name: Windows 8 and Windows Server 2012 href: install/on-windows-8.md + - name: Windows Server 2025 + href: install/on-server-2025.md - name: Windows Server 2022 href: install/on-server-2022.md - name: Windows Server 2019 @@ -696,7 +698,7 @@ items: - name: October 2024 security and quality rollup href: release-notes/2024/10-08-october-security-and-quality-rollup.md - name: October 2024 cumulative update preview - href: release-notes/2024/10-22-october-preview-cumulative-update.md + href: release-notes/2024/10-22-october-preview-cumulative-update.md - name: November 2024 security and quality rollup href: release-notes/2024/11-12-november-security-and-quality-rollup.md - name: November 2024 cumulative update preview diff --git a/docs/fsharp/get-started/get-started-vscode.md b/docs/fsharp/get-started/get-started-vscode.md index 20d0825706fc7..e8f8a5c9217e8 100644 --- a/docs/fsharp/get-started/get-started-vscode.md +++ b/docs/fsharp/get-started/get-started-vscode.md @@ -7,7 +7,8 @@ ms.date: 10/29/2021 You can write F# in [Visual Studio Code](https://code.visualstudio.com) with the [Ionide plugin](https://marketplace.visualstudio.com/items?itemName=Ionide.Ionide-fsharp) to get a great cross-platform, lightweight Integrated Development Environment (IDE) experience with IntelliSense and code refactorings. Visit [Ionide.io](https://ionide.io) to learn more about the plugin. -To begin, ensure that you have [F# and the Ionide plugin correctly installed](install-fsharp.md#install-f-with-visual-studio-code). +> [!NOTE] +> Before beginning, ensure you've installed both [F# and the Ionide plugin](install-fsharp.md#install-f-with-visual-studio-code). ## Create your first project with Ionide diff --git a/docs/navigate/devops-testing/toc.yml b/docs/navigate/devops-testing/toc.yml index 78fa8c1354859..fd3d693eb78f5 100644 --- a/docs/navigate/devops-testing/toc.yml +++ b/docs/navigate/devops-testing/toc.yml @@ -559,16 +559,22 @@ 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 publishing tutorial + href: ../../core/containers/sdk-publish.md + - name: .NET SDK publishing reference + 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: 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 href: /visualstudio/containers/overview?toc=/dotnet/navigate/devops-testing/toc.json&bc=/dotnet/breadcrumb/toc.json - name: .NET distribution packaging diff --git a/docs/standard/base-types/snippets/character-encoding-introduction/csharp/InsertNewlines.cs b/docs/standard/base-types/snippets/character-encoding-introduction/csharp/InsertNewlines.cs index c0254bf97f78d..eb36a20f2a934 100644 --- a/docs/standard/base-types/snippets/character-encoding-introduction/csharp/InsertNewlines.cs +++ b/docs/standard/base-types/snippets/character-encoding-introduction/csharp/InsertNewlines.cs @@ -35,7 +35,7 @@ static string InsertNewlinesEveryTenTextElements(string input) while (enumerator.MoveNext()) { builder.Append(enumerator.Current); - if (textElementCount % 10 == 0 && textElementCount > 0) + if (textElementCount % 10 == 0) { builder.AppendLine(); // newline }