.NET SDK container creation overview
While it's possible to containerize .NET apps with a Dockerfile, there are compelling reasons for containerizing apps directly with the .NET SDK. 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 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 totrue
. This property is implicitly set totrue
for executable project types, such asconsole
,webapp
, andworker
.EnableSdkContainerSupport
: Set totrue
when your project type is a console app.
To explicitly enable SDK container support, consider the following project file snippet:
<PropertyGroup>
<IsPublishable>true</IsPublishable>
<EnableSdkContainerSupport>true</EnableSdkContainerSupport>
</PropertyGroup>
Publish switches and build properties
As with all .NET CLI commands, you can specify MSBuild properties on the command line. 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. They provide a detailed trace of the build process, essential for MSBuild analysis. For more information, see Troubleshoot and create logs for MSBuild.
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:
dotnet publish -p PublishProfile=DefaultContainer
Which sets the PublishProfile
property to DefaultContainer
, is equivalent to the following command:
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
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
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 <registry> -u <username> -p <password>
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"<token>"
.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. To add new insecure registries, new hosts are added to the "insecure-registries"
array property:
{
"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
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
.
[[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.
$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
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, but uses the same mechanisms and, importantly, adheres to the same opt-out controls.
The telemetry gathered is intended to be general in nature and not leak any personal information—the intended purpose is to help measure:
- Usage of the .NET SDK containerization feature overall.
- Success and failure rates, along with general information about what kinds of failures happen most frequently.
- Usage of specific features of the tech, like publishing to various registry kinds, or how the publish was invoked.
To opt-out of telemetry, set the DOTNET_CLI_TELEMETRY_OPTOUT
environment variable to true
. For more information, see .NET CLI telemetry.
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 |