Skip to content

Building multi-platform Docker Image for .NET #1

@UchiTesting

Description

@UchiTesting

⚠Commit 2d13e55 tells about working Docker image. It actually refers to the fact of obtaining a complete multi-platform Docker image. As it was actually complied for ARM64 architecture, it will likely not work on AMD64 hosts.

Setup

When we build on a machine where Docker was installed with Docker Desktop, everything is ready to go.
On other OS (especially Linux) extra content needs to be installed.

docker run --privileged --rm tonistiigi/binfmt --install all

Builder

Since version 18.09 there is a new builder bundled with Docker.
It comes with a default builder so that it could be ready to use. That said the documentation recommends to create and use a custom one for the sake of ease of use. Then custom builders can be deleted and recreated at will to restart fresh in case of trouble.

We use buildx and a custom builder.

docker buildx create --name dotnetbuilder --driver docker-container --bootstrap --driver-opt "network=dotnet" --use

--driver-opt "network=dotnet" means we will use the dotnet custom docker network with that builder.

Useful commands are

  • docker buildx ls: Listing the available builders can be handy especially to check that it is up and running
  • docker buildx use {builderName}: The ls command above displays a star next to the default builder. Hence we can check if it needs to be changed with the use command
  • docker buildx inspect --bootstrap: Displays information about a builder. Handy --bootstrap switch allows to start a builder should it be in inactive state.
  • docker buildx build {options}: The main command. Builds in Docker cache is neither --push (Push to Docker registry, likely Docker hub) nor --load (load in the local instance of Docker) are given. A warning message will appear then.

Dockerfile

The principle is to build from AMD64 images yet being able to build images for different platforms especially ARM64.

⚠ NB: The different dotnet CLI command explicitely mention ARM64 as a target. Hence as is we are compiling for ARM64 architecture yet containerize into images for different platforms. The string to represent platform between Docker platforms and .NET RID differ.

Architecture .NET Docker
AMD64 linux-amd64 linux/amd64
ARM64 linux-arm64 linux/arm64

This is currently the missing part to make fully functional multi-platform images

Docker uses 2 variables to manage cross-building for several architectures.

  • BUILDPLATFORM: Refers to the platform from which be build the images
  • TARGETPLATFORM: Refers to the platforms we aim for.

We can specify the platform for an image in a Dockerfile by precising the --platform switch before the image name.

In the following Dockerfile, we take advantage of those informations to prepare a container for the target platform on the .NET runtime image.
For the time being, it is just preparing a target location to put our application and exposing the necessary ports.

On the next image we enforce the use of the build platform on the .NET SDK image.
This is important because otherwise specifying target platforms that differ from your building platform may lead to issues otherwise.

The SDK image indeed has dotnet CLI for us to use.

There are sequential steps in order to publish a .NET application from the CLI.

  • Restore: It is about gathering dependencies in which NuGet packages.
  • Build: It is about compiling a runnable executable most likely for development purpose.
  • Publish: It is about compiling the application and gathering any assets so it becomes ready to deploy.

Kind of like Maven, asking for a further stage will trigger previous stages.
Hence build will trigger a restore as well as asking for a publish will trigger a build (and by extension a restore).
However in the context of pipelines it is advised to run those commands separately let alone to segregate the details for each step.

Notice that for the restore step we only need the .NET project file. So for the sake of build performance, only that file is copied to the SDK image. The rest is copied for the need of the build later on.

The final step is to transfer the published application to the target .NET runtime image and precise what application should be run when the container is run.

FROM --platform=$TARGETPLATFORM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY ["rasp-test/rasp-test.csproj", "."]
RUN dotnet restore "rasp-test.csproj" -r linux-arm64
COPY rasp-test/. .
RUN dotnet build "rasp-test.csproj" -c Release -r linux-arm64 --no-self-contained -o /app/build

FROM build AS publish
RUN dotnet publish "rasp-test.csproj" -r linux-arm64 -c Release --no-self-contained -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "rasp-test.dll"]

Locally I ran the following command:

docker buildx build --platform linux/amd64,linux/arm64 -t uchitesting/rasp-test:multi -f .\Dockerfile-multi . --push

Source:
Multi-platform images @Docker Docs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions