Docker Images can be generated in dotnet projects without a Dockerfile. Did you know?

2023, Nov 03

Did you know that you can generate Docker Images in dotnet projects? Traditionally we would use a Dockerfile to generate Docker Images, but that's not the only way.

Dockerize a console app

Consider that you have a console app and you want to generate a Docker Image for it. You can do it by adding some additional lines to your .csproj file.

Before:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

After:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <ContainerRepository>console</ContainerRepository>
    <ContainerImageTag>1.2.3</ContainerImageTag>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Build.Containers" Version="8.0.100-rc.2.23480.5" />
  </ItemGroup>

</Project>

When you compare you will notice the addition of ContainerRepository, which represents the Image name, and ContainerImageTag, which represents the Image tag.

There are many more attributes that you can use to match Dockerfile capabilities on dotnet, check the official Microsoft documentation.

Because this is not a Web project, the package reference to Microsoft.NET.Build.Containers needs to be added to the console project.

At the time of this writing, the latest version of Microsoft.NET.Build.Containers is 8.0.100-rc.2.23480.5.

Then run the following CLI command:

dotnet publish -r linux-x64 /t:PublishContainer

You will notice that the os is linux and the architecture is x64, represented together by r, the Runtime Identifier. When the command above is run, dotnet CLI is going to generate a Dockerfile leveraging the package Microsoft.NET.Build.Containers inferring:

  • Which base image to use.
  • Which version of the base image to use.
  • Where to push the generated image.

baseImage

To run the image locally as a container, run the following command:

docker run console:1.2.3

Dockerize a web app

Similarly to the console app, the same can be done for web projects. The only difference is that the package reference to Microsoft.NET.Build.Containers is not needed, as the project leverages Microsoft.NET.Sdk.Web sdk.

Before:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <InvariantGlobalization>true</InvariantGlobalization>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
  </ItemGroup>

</Project>

After:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <InvariantGlobalization>true</InvariantGlobalization>
    <ContainerRepository>weatherapi</ContainerRepository>
    <ContainerBaseImage>mcr.microsoft.com/dotnet/aspnet:8.0</ContainerBaseImage>
    <ContainerImageTag>1.0.0</ContainerImageTag>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
  </ItemGroup>

</Project>

When you compare you will notice the addition of ContainerRepository, which represents the Image name, ContainerBaseImage representing the base image and ContainerImageTag, which represents the Image tag.

There are many more attributes that you can use to match Dockerfile capabilities on dotnet, check the official Microsoft documentation.

Then run the following CLI command:

dotnet publish --os linux --arch x64 -p:PublishProfile=DefaultContainer

baseImageWeb

To run the image locally as a container, run the following command:

docker run -p 5000:8080 weatherapi:1.0.0

In .NET 8.0, the internal port number for web projects starts on port 8080 by default. This is because of the rootless execution on port 8080, not requiring root permission as if it would run on port 80.

Check the repository example on PlayGoKids repository to validate the commands yourself.