-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable using docker build --platform
switch (easily)
#4388
Comments
[Triage] Long-term we need to come up with a design that can work. Ideally it would be great if we could eliminate the need for a set of variant-less tags (e.g. This also affects the scaffolded Dockerfiles produced by the VS Container Tools. They are generating the old "multi-arch" Dockerfile that no longer works with .NET 7 when targeting an architecture that differs from the host. |
@richlander, In your proposed Dockerfile
Have you considered utilizing the
|
That looks awesome! Ship it! I didn't know about that pattern. That means we just need to fix the RID problem. |
Just wonder about this and Native AOT. Will this pattern not work for that? I think it doesn't have a cross-arch cross-build story. Is that right @jkotas? |
NativeAOT has cross-arch cross-build story. It is documented here: https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/docs/compiling.md#cross-architecture-compilation . For Linux, it requires rootfs matching the target platform. |
If it helps, https://github.com/dotnet/samples/tree/main/core/nativeaot/HelloWorld is nativeaot sample that includes Linux x64 and Windows x64 docker files for building it (the sample does not have cross-arch build support). |
Issue for NativeAOT scenario investigation is at #4129 |
The RID fix is in, thanks to a new hire on our team @JL03-Yue who worked on it. 😄 Is there anything else required here @richlander ? |
It works great! I tested it with dotnetapp and the following Dockerfile. To recap, we want to be able to build Arm64 and x64 assets on an Apple Arm64 machine safetly and correctly. We now can (with .NET 8 Preview 3). We are going to look at backporting the change to .NET 7 as well. The Dockerfile references a multi-platform tag that references Amd64, Arm64, and Arm32 images that include a .NET 8 Preview 3 SDK. Note: This test repo will be deleted before long, so don't be surprised if you read this later and the sample doesn't work. You'll need to switch to the real .NET 8 SDK image. It's called "dotnetnonroot" since I was using for testing another feature. # To learn about building .NET container images:
# https://github.com/dotnet/dotnet-docker/blob/main/samples/README.md
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/nightly/sdk:8.0-preview AS build
ARG TARGETARCH
WORKDIR /source
# copy csproj and restore as distinct layers
COPY *.csproj .
RUN dotnet restore -a $TARGETARCH
# copy and publish app and libraries
COPY . .
RUN dotnet publish -a $TARGETARCH --self-contained false --no-restore -o /app
# To enable globalization:
# https://github.com/dotnet/dotnet-docker/blob/main/samples/enable-globalization.md
# final stage/image
FROM mcr.microsoft.com/dotnet/runtime:7.0-jammy
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["./dotnetapp"] Some quick tests on my Apple M1 machine. % docker build -t dotnetapp .
% docker inspect dotnetapp -f "{{.Os}}\{{.Architecture}}"
linux\arm64
% docker run --rm dotnetapp | grep Arch
OSArchitecture: Arm64
% docker build -t dotnetapp --platform linux/arm64 .
% docker inspect dotnetapp -f "{{.Os}}\{{.Architecture}}"
linux\arm64
% docker run --rm dotnetapp | grep Arch
OSArchitecture: Arm64
% docker build -t dotnetapp --platform linux/amd64 .
% docker inspect dotnetapp -f "{{.Os}}\{{.Architecture}}"
linux\amd64
% docker build -t dotnetapp --platform linux/arm .
% docker inspect dotnetapp -f "{{.Os}}\{{.Architecture}}"
linux\arm I didn't run the generated Amd64 and Arm32 images on my Arm64 machine since they don't work in that environment. The key point is that you can build images for all the architectures with one Dockerfile using the I tested the same scenarios and they worked on my x64 machine as well. We can have even more fun with % docker buildx build -f Dockerfile.ubuntu --platform linux/amd64,linux/arm64,linux/arm -t dotnetnonroot.azurecr.io/dotnetapp --push . On my Apple M1 machine: % docker run --rm -it dotnetnonroot.azurecr.io/dotnetapp
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP!!!!!!! 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
.NET 7.0.3
Ubuntu 22.04.2 LTS
UserName: root
OSArchitecture: Arm64
ProcessorCount: 4
TotalAvailableMemoryBytes: 4124512256 (3.00 GiB) On my x64 machine: # docker run --rm -it dotnetnonroot.azurecr.io/dotnetapp
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP!!!!!!! 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
.NET 7.0.3
Ubuntu 22.04.2 LTS
UserName: root
OSArchitecture: X64
ProcessorCount: 12
TotalAvailableMemoryBytes: 33444470784 (31.00 GiB)
cgroup memory constraint: /sys/fs/cgroup/memory/memory.limit_in_bytes
cgroup memory limit: 9223372036854771712 (8589934591.00 GiB)
cgroup memory usage: 6713344 (6.00 MiB)
GC Hard limit %: 0 On my Arm64 Raspberry Pi: $ docker run --rm -it dotnetnonroot.azurecr.io/dotnetapp
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP!!!!!!! 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
.NET 7.0.3
Ubuntu 22.04.2 LTS
UserName: root
OSArchitecture: Arm64
ProcessorCount: 4
TotalAvailableMemoryBytes: 3978678272 (3.00 GiB) |
This experience will ship in:
|
If you want this experience now, you can use: |
Excellent news @richlander! Thanks for this. I've tried this on my M1 and on a x64 machine and was able to successfully build amd64 and arm64 with buildx on both.
|
[I updated my original question as I figured out why it failed.] In case it helps somebody else, it was not obvious to me that the builder and runtime platforms have to be the same platform kind, else the runtime library dependencies will not match. I built And
When my deployment layer was built using The correct build should be
If you mix build platforms you will get a |
@ptr727 100% correct. I have run into this same problem. However, it isn't specific to this new pattern. This has always been a pitfall. In fact, I just ran into this same problem (just a few minutes ago) building some Go code. |
Working through this thread with a version of @richlander 's great example but I am noticing something strange here. This is my docker file...
I was running into issues with I will paste the full output below, but the relevant lines are:
for intel, and for arm:
Thus my tests are never getting run as the if statement never fires. Am I missing something here? docker buildx build -f dockerfile.api --push -t markmcgookin/some-project:20230421.1 -t markmcgookin/some-project:latest --platform linux/amd64,linux/arm64 .
[+] Building 72.5s (42/42) FINISHED
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build definition from dockerfile.api 0.0s
=> => transferring dockerfile: 1.09kB 0.0s
=> [linux/arm64 internal] load metadata for mcr.microsoft.com/dotnet/aspnet:7.0-bullseye-slim 0.2s
=> [linux/amd64 internal] load metadata for mcr.microsoft.com/dotnet/aspnet:7.0-bullseye-slim 0.2s
=> [linux/arm64 internal] load metadata for mcr.microsoft.com/dotnet/nightly/sdk:8.0-preview 0.3s
=> [linux/arm64 build 1/14] FROM mcr.microsoft.com/dotnet/nightly/sdk:8.0-preview@sha256:d7da5c8aa2963c312ffeb43951755a4651ebdfde5f79230a37d2038cba5 0.0s
=> => resolve mcr.microsoft.com/dotnet/nightly/sdk:8.0-preview@sha256:d7da5c8aa2963c312ffeb43951755a4651ebdfde5f79230a37d2038cba547071 0.0s
=> [linux/arm64 final 1/3] FROM mcr.microsoft.com/dotnet/aspnet:7.0-bullseye-slim@sha256:ff588d989020412cd2d0f2781a2c1e7a144811d405eb865d2280e285d861 0.0s
=> => resolve mcr.microsoft.com/dotnet/aspnet:7.0-bullseye-slim@sha256:ff588d989020412cd2d0f2781a2c1e7a144811d405eb865d2280e285d861de4d 0.0s
=> CACHED [linux/amd64 final 1/3] FROM mcr.microsoft.com/dotnet/aspnet:7.0-bullseye-slim@sha256:ff588d989020412cd2d0f2781a2c1e7a144811d405eb865d2280e 0.0s
=> => resolve mcr.microsoft.com/dotnet/aspnet:7.0-bullseye-slim@sha256:ff588d989020412cd2d0f2781a2c1e7a144811d405eb865d2280e285d861de4d 0.0s
=> [internal] load build context 0.4s
=> => transferring context: 29.01MB 0.3s
=> CACHED [linux/arm64 build 2/14] WORKDIR /src 0.0s
=> CACHED [linux/arm64 build 3/14] COPY [api/*.csproj, api/] 0.0s
=> CACHED [linux/arm64 build 4/14] COPY [tests/*.csproj, tests/] 0.0s
=> CACHED [linux/arm64->amd64 build 5/14] RUN dotnet restore "api/some-project-api.csproj" -a amd64 0.0s
=> CACHED [linux/arm64->amd64 build 6/14] RUN dotnet restore "tests/some-project-tests.csproj" -a amd64 0.0s
=> CACHED [linux/arm64->amd64 build 7/14] COPY [api/., api/] 0.0s
=> CACHED [linux/arm64->amd64 build 8/14] COPY [tests/., tests/] 0.0s
=> CACHED [linux/arm64->amd64 build 9/14] WORKDIR /src/api 0.0s
=> CACHED [linux/arm64->amd64 build 10/14] RUN dotnet build "some-project-api.csproj" -c Release -a amd64 0.0s
=> CACHED [linux/arm64->amd64 build 11/14] WORKDIR /src 0.0s
=> CACHED [linux/arm64->amd64 build 12/14] RUN echo "Target: amd64" 0.0s
=> CACHED [linux/arm64->amd64 build 13/14] RUN echo "Build: $BUILDPLATFORM" 0.0s
=> CACHED [linux/arm64->amd64 build 14/14] RUN if [ "amd64" = "$BUILDPLATFORM" ] ; then dotnet test "tests/some-project-tests.csproj" -a amd64 ; fi 0.0s
=> CACHED [linux/arm64->amd64 publish 1/2] WORKDIR /src/api 0.0s
=> [linux/arm64->amd64 publish 2/2] RUN dotnet publish "some-project-api.csproj" -c Release -o /app/publish -a amd64 1.3s
=> CACHED [linux/arm64 final 2/3] WORKDIR /app 0.0s
=> CACHED [linux/arm64 build 5/14] RUN dotnet restore "api/some-project-api.csproj" -a arm64 0.0s
=> CACHED [linux/arm64 build 6/14] RUN dotnet restore "tests/some-project-tests.csproj" -a arm64 0.0s
=> CACHED [linux/arm64 build 7/14] COPY [api/., api/] 0.0s
=> CACHED [linux/arm64 build 8/14] COPY [tests/., tests/] 0.0s
=> CACHED [linux/arm64 build 9/14] WORKDIR /src/api 0.0s
=> CACHED [linux/arm64 build 10/14] RUN dotnet build "some-project-api.csproj" -c Release -a arm64 0.0s
=> CACHED [linux/arm64 build 11/14] WORKDIR /src 0.0s
=> CACHED [linux/arm64 build 12/14] RUN echo "Target: arm64" 0.0s
=> CACHED [linux/arm64 build 13/14] RUN echo "Build: $BUILDPLATFORM" 0.0s
=> CACHED [linux/arm64 build 14/14] RUN if [ "arm64" = "$BUILDPLATFORM" ] ; then dotnet test "tests/some-project-tests.csproj" -a arm64 ; fi 0.0s
=> CACHED [linux/arm64 publish 1/2] WORKDIR /src/api 0.0s
=> CACHED [linux/arm64 publish 2/2] RUN dotnet publish "some-project-api.csproj" -c Release -o /app/publish -a arm64 0.0s
=> CACHED [linux/arm64 final 3/3] COPY --from=publish /app/publish . 0.0s
=> [linux/amd64 final 2/3] WORKDIR /app 0.0s
=> [linux/amd64 final 3/3] COPY --from=publish /app/publish . 0.0s
=> exporting to image 70.5s
=> => exporting layers 0.3s
=> => exporting manifest sha256:126ec6a256c0b985b34890e294ca564185c2c5953cf801b4059f8b5130f22e9e 0.0s
=> => exporting config sha256:91f49866a4cf7bbbc4015c52e20e2906418c940304e733a2f4c2b2c9a5ac684e 0.0s
=> => exporting attestation manifest sha256:9b702e17c9536fefee0baebef6b0e441657875beb9a0d301eccffe4268e63bb4 0.0s
=> => exporting manifest sha256:804482a55a15a84769ac0fe837b586eb4f254d635073651751f4df36bf69226e 0.0s
=> => exporting config sha256:f3ad2f6c8d294082c1c2670c627653beb8fb294fb7c477a1da4540cf0085bb98 0.0s
=> => exporting attestation manifest sha256:ec0a6aa61f65a3d917e7b766caee3107dcde5d71935cce6465441cd105f3afd9 0.0s
=> => exporting manifest list sha256:c5b30f49682e9bdf619777119256092e1e18d6cd8751d2047335dba94b3884af 0.0s
=> => pushing layers 1.8s
=> => pushing manifest for docker.io/markmcgookin/some-project:20230421.1@sha256:c5b30f49682e9bdf619777119256092e1e18d6cd8751d2047335dba94b3884af 1.9s
=> => pushing manifest for docker.io/markmcgookin/some-project:latest@sha256:c5b30f49682e9bdf619777119256092e1e18d6cd8751d2047335dba94b3884af 0.9s
|
@markmcgookin looking at all of those |
Sorry, I've run this a load and chopped and changed a lot of things, but its exactly the same. Just used
(The project name is different, because I replaced it last time, then realised this is a test app and no-one cares) |
@markmcgookin your problem is a little different to mind, but I too noticed that For me, I think that because there is an SDK version mismatch (i.e. I am trying to test a .NET 7 .csproj file with SDK version 8), I was getting errors telling me to change the .NET version, in my .csproj or install the .NET 7 SDK. It seems that For now I am just waiting for 7.0.3xx to be released, since it will have this Docker fix too. |
If the arg is not set, maybe explicitly declare the arg. E.g. this is what I ended up using: https://github.com/ptr727/PlexCleaner/blob/develop/Docker/Debian.dotNET.Dockerfile E.g. builder layer:
e.g. final layer
|
Yes. The ARGS are not set by default. You have to set them via the pattern that @ptr727 is using. Same thing as here: https://gist.github.com/richlander/70cde3f0176d36862af80c41722acd47. The pattern in |
I am happy to create (or update) a sample that shows how to do unit testing as is being demonstrated here. I didn't think of that initially. Good scenario! |
Thanks @richlander that would be great. So the args are defined by default but I need to set them. Cool. I misunderstood. Thanks all for the help |
No worries. It's simple once you know how it all works and quite mysterious before that. It took us a bit to figure out these patterns. And aspects of it are not intuitive because of the way Dockerfiles work, including various non-obvious scoping. |
This is closed (as are the dozens of similar issues), does that indicate the problem is now solved for .Net 7? If so, what is the definitive solution? |
Great question. The problem is fixed in .NET 8 Preview 3 and 7.0.300. The latter hasn't shipped yet. I don't have a date handy on when that will be. However, you can use .NET 8 Preview 3 to build .NET 7 code as a workaround in the time. Also, you don't need the fix if you use |
@richlander Gotcha, and by using .Net 8 Preview 3 only for the build step, that means using that are the image in the Dockerfile instead of the .Net 7 base image correct? We are using We don't use the runtime flag for |
Right. The SDK version you use doesn't matter for the final app/image.
If you are not using |
Hello @richlander , Dockerfile:
Build command: Run command: I receive the following error:
If I try to check the container contents using The container does load properly when I don't use the Thanks! |
Retracting this... it does work as intended. When replacing the image, I removed the |
@richlander Hi, I trying to solve this for days without success. I followed all of your steps but when I do that I get stuck on dotnet restore command which causes problem with package downgrade. It's just a small part of that error, it's a long list.
When I remove -a tags, then I guess it does not build for the proper platform. Here is my Dockerfile. Any help is appreciated. # Defines SDK image to build the apliaction
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/nightly/sdk:8.0-preview AS build-env
ARG TARGETARCH
# Creates folder "src" inside container and set it as current folder
WORKDIR /src
# Copies all csproj files as they are needed for restore function
COPY *.sln .
COPY CorporateGames.API/*.csproj CorporateGames.API/
COPY CorporateGames.Application/*.csproj CorporateGames.Application/
COPY CorporateGames.Core/*.csproj CorporateGames.Core/
# Restores all packages from main project (CorporateGames.API)
RUN dotnet restore -a $TARGETARCH
# Copies all files from project folder into src folder inside container
COPY . .
# Go into CorporateGames.API folder as we'll publish only that project
WORKDIR /src/CorporateGames.API
# Build the project
RUN dotnet build CorporateGames.API.csproj -c Staging --no-restore -a $TARGETARCH
# Publishes the project into publish folder inside container
RUN dotnet publish CorporateGames.API.csproj -c Staging -o /publish --no-build --no-restore -a $TARGETARCH
# Defines runtime image to run the application
FROM mcr.microsoft.com/dotnet/aspnet:7.0 as runtime
# Set current folder
WORKDIR /src/CorporateGames.API/publish
# Copy the publish directory from the build-env stage into the runtime image
COPY --from=build-env /publish .
ENV ASPNETCORE_URLS=http://+:5000
ENV ASPNETCORE_ENVIRONMENT=Staging
EXPOSE 5000
ENTRYPOINT ["dotnet", "CorporateGames.API.dll"] |
@vukasinpetrovic -- Package downgrades should be unrelated. If you remove all this fancy architecture targeting stuff, I assume the package downgrades still exist. Is that true? @iliashkolyar -- This pattern enables successful building. The resultant x64 image may or may not successfully run in QEMU, however. I just build a .NET 8 app/image with this pattern and (to my surprise) it ran as x64 on my M1 machine w/o issue. Our samples are being updated to this pattern. #4742 Apparently, this ENV may also help: |
@richlander You are right, that problem with downgrades is not related to docker, but restoring project packages on arm that targets amd64 platform (if I specify architecture/runtime while doting dotnet restore). I'll have to research that one. If you had that problem also, any info would be much appreciated. |
I didn't run into that problem. If you have a repro, that would be useful. The repro won't need your app, just some subset of your project file. |
Hi there, this doesnt work for me. I read this and all related subjects.
on my m1 mac
run on m1
run on amd64
dotnet sdk: 7.0.401 |
Hi, stuck in problem: When build on my mac m1 via command
And reverse behavior, when build on amd64 and run on m1 have same error. Dockerfile looks like this
Yes, workaround is build on amd64 machine > push > run on arm64 via emulation. May be have some missunderstanding and cant build on 1 machine 2 different arch? |
There is no way to write a Dockerfile for .NET that is (A) succinct, (B) easy to build from the command line, (C) can equally produce Arm64 or x64 images, (D) that will run equally well on both Arm64 and x64 machines, and (E) avoids running the SDK in an emulator (since .NET doesn't support running in QEMU).
The following Dockerfile (which doesn't currently work) would satisfy all four of these requirements.
Note: The two ENVs are set via Docker, not my invention. Context: #4387 (comment). The reason this
Dockerfile
doesn't work is because-r
expectsx64
notamd64
(which$TARGETARCH
returns) and our container tags expectarm64v8
notarm64
(which$BUILDARCH
returns). Funny enough,$TARGETARCH
returnsarm64
for Arm64, which-r
likes fine and$BUILDARCH
returnsamd64
for x64, which our container tags like fine.Note: Close readers may wonder why
$TARGETARCH
is not needed for the lastFROM
statement. That's because--platform
is picking the correct image from the multi-arch tag. We could use $TARGETARCH
if we wanted, but it is unnecessary since the underlying mechanics will do the right thing. How would one know that the tag is a multi-arch tag just by looking at it? It's because it doesn't include an architecture.It would enable the following scenarios:
docker build
-- will produce a native-arch result on both Arm64 and x64 and use the native arch SDK.docker build --platform linux/arm64
-- Will produce an Arm64 container image on both Arm64 and x64 machines but run the SDK as native-arch, avoiding emulation (on x64 machines).docker build --platform linux/amd64
-- Same as above, but will produce an x64 container images, and similarly avoid emulation (on Arm64 machines).docker buildx build --platform linux/amd64,linux/arm64
-- Same as above, but will produce both Arm64 and x64 container images, and similarly avoid emulation.Note that the resulting image will not run in emulation. That's the not the purpose of this proposal. Instead, the purpose is to reliably avoid emulation and to make it easy, for example, to build x64 container images on an Apple M1 box and push those to an x64 cloud. You'd be able to do that via the
--platform
switch and not need to do anything else (other than follow the pattern used in the Dockerfile).It requires two things:
arm64
(matching$BUILDARCH
) in addition toarm64v8
.linux-amd64
(matching$TARGETARCH
) withlinux-x64
The proposal is to make these changes for .NET 6+.
The alternative is the following.
Inspiration: #4387 (comment)
SDKARCH
needs to have a default, so you need to know to set theARG
on whatever platform isn't the default. Here, I'm choosing x64 (or really "amd64") as the default. Also, you cannot run any code before the firstFROM
since a Dockerfile isn'tbash
.This pattern enables the following scenarios:
docker build
-- only works correctly on x64 and breaks on Arm64 (dotnet restore
just hangs),docker build --build-arg SDKARCH=arm64v8
-- builds an Arm64 image on an Arm64 machine, not because theARG
is set, however. TheARG
just enables the SDK to run with the native arch.docker build --platform linux/arm64
-- Doesn't do anything useful, same for specifyinglinux/amd64
.docker build buildx --platform linux/amd64,linux/arm64
-- Doesn't work.docker build buildx --platform linux/amd64,linux/arm64 --build-arg SDKARCH=arm64v8
-- Produces both Arm64 and x64 container images.The text was updated successfully, but these errors were encountered: