Skip to content

FROM --platform=$BUILDPLATFORM a as b not overriding TARGETPLATFORM for multi-stage builds #4929

Open

Description

@hanikesn

I'm currently investing in making our builds cross-platorm compatible. I want to e.g. download the uv package manager and use it in both our build and target images. I then hit the following issue, when trying to keep our build instructions DRY:

FROM busybox as base
ARG TARGETPLATFORM
RUN echo $TARGETPLATFORM > /plt
FROM --platform=$TARGETPLATFORM base as base-target
FROM --platform=$BUILDPLATFORM base as base-build

I would expect this to build two different variants of the base stage when BUILD!=TARGET, one for each. Like this works for multi platform images pulled from the registry. But this is not the case:

#On linux/amd64:
$ docker build --platform=linux/arm64 -f test.Dockerfile --target=base-build .
...
 => CACHED [base 2/2] RUN echo linux/arm64 > /plt
...
$ docker build --platform=linux/arm64 -f test.Dockerfile --target=base-target .
...
 => CACHED [base 2/2] RUN echo linux/arm64 > /plt
...
FROM --platform=$TARGETPLATFORM alpine as base-target
FROM --platform=$BUILDPLATFORM alpine as base-build

Here's the different behavior with images from the registry:

$ docker build --platform=linux/arm64 -f test.Dockerfile --target=base-build -t target .
$ docker run --rm -it target cat /etc/apk/arch
aarch64
$ docker build --platform=linux/arm64 -f test.Dockerfile --target=base-build -t build .
$ docker run --rm -it build cat /etc/apk/arch
x86_64

It seems like there's no other way to get this working besides duplicating the base stage for the different platforms and giving them a unique stage name. I'm happy to hear any other workarounds.

Possibly related to

Activity

  1. tonistiigi commented on May 15, 2024

    @tonistiigi
    Member

    Yes, this is same as #4237 . --platform is a hint for pulling the image if it is a manifest list. In your case it is a stage that is already resolved to a specific platform.

    Implicitly your code is:

    FROM --platform=$TARGETPLATFORM busybox as base
    ARG TARGETPLATFORM
    RUN echo $TARGETPLATFORM > /plt
    FROM --platform=$TARGETPLATFORM base as base-target
    FROM --platform=$BUILDPLATFORM base as base-build
    

    Additionally --platform=$BUILDPLATFORM does not change the meaning of TARGETPLATFORM inside the stage.

    FROM --platform=$BUILDPLATFORM alpine
    ARG TARGETPLATFORM BUILDPLATFORM
    RUN env && echo "These do not match in cross compiling" 
    
  2. hanikesn commented on May 16, 2024

    @hanikesn
    Author

    Additionally --platform=$BUILDPLATFORM does not change the meaning of TARGETPLATFORM inside the stage.

    So basically FROM --platform=$BUILDPLATFORM doesn't do anything for intermediate stages? I guess I agree with AkihiroSuda from the other thread that that is quite surprising and a warning would be nice. Also the docs are very lightweight on this topic.

    Do you think there's any other way to achieve what I want (besides duplication)?

  3. tonistiigi commented on May 20, 2024

    @tonistiigi
    Member

    doesn't do anything for intermediate stages?

    It only has an effect atm if the source parameter for FROM is multi-platform image or multi-platform build result(via named context). The stage itself can be any stage in the Dockerfile.

    Do you think there's any other way to achieve what I want (besides duplication)?

    If the commands are same for the stages defined with different platform sources(that result in same platforms based on config) then buildkit will internally merge the commands and they only run once.

  4. huww98 commented on May 24, 2024

    @huww98

    @tonistiigi I think we are hitting the same issue. In my case, I want to insert an arch independent step into the build process, so that it does not need to be done multiple times for each arch.

    With this Dockerfile (I'm building on an arm64 host)

    FROM busybox as env
    # install build deps
    
    FROM --platform=$BUILDPLATFORM env as gen
    # download source code & ./autogen.sh
    RUN uname -m && [ $(uname -m) = "aarch64" ] && mkdir /src
    
    FROM env as build
    # build source code, mount source code from gen stage
    RUN --mount=type=bind,from=gen,source=/src,target=/src,rw \
        echo "building"

    I expect the build to success, but I got

     => ERROR [linux/amd64 gen 1/1] RUN uname -m && [ $(uname -m) = "aarch64" ] && mkdir /src                                                                   0.1s
     => CACHED [linux/arm64 gen 1/1] RUN uname -m && [ $(uname -m) = "aarch64" ] && mkdir /src                                                                  0.0s
     => [linux/arm64 build 1/1] RUN --mount=type=bind,from=gen,source=/src,target=/src,rw     echo "building"                                                   0.1s
    ------
     > [linux/amd64 gen 1/1] RUN uname -m && [ $(uname -m) = "aarch64" ] && mkdir /src:
    0.073 x86_64
    ------
    Dockerfile.test:6
    --------------------
       4 |     FROM --platform=$BUILDPLATFORM env as gen
       5 |     # download source code & ./autogen.sh
       6 | >>> RUN uname -m && [ $(uname -m) = "aarch64" ] && mkdir /src
       7 |     
       8 |     FROM env as build
    --------------------
    error: failed to solve: process "/bin/sh -c uname -m && [ $(uname -m) = \"aarch64\" ] && mkdir /src" did not complete successfully: exit code: 1
    

    I would expect the gen stage only run once on arm64, but it also runs on amd64. Any suggestions please?

  5. hanikesn commented on May 24, 2024

    @hanikesn
    Author

    I would expect the gen stage only run once on arm64, but it also runs on amd64. Any suggestions please?

    You need to do something similar to this:

    FROM busybox-build as uv-amd64
    # install deps
    FROM busybox-build as uv-arm64
    # install deps
    FROM uv-$BUILDARCH as uv-build
    FROM uv-$TARGETARCH as uv-target
  6. huww98 commented on May 24, 2024

    @huww98

    I kind of understand the current behavior. But this is still confusing. Do you consider overriding the TARGETPLATFORM of stage as a valid feature request, and to be included in the future releases? If so, I can investigate more and may create PR for this.

    The workaround currently requires duplicating many code in Dockerfile, or just spliting the Dockerfile into multiple Dockerfile and build them in sequence. They are not ideal.

  7. tonistiigi commented on May 24, 2024

    @tonistiigi
    Member

    Do you consider overriding the TARGETPLATFORM of stage as a valid feature request,

    Supporting FROM --platform=.. <basestage> essentially means that the basestage needs to be evaluated multiple times.

    Eg.

    FROM --platform=amd64 alpine AS base
    RUN cmd
    
    FROM --platform=arm64 base
    COPY --from=base /foo /bar
    RUN smth
    

    Building this Dockerfile with default args would run cmd twice for both architectures (in the internal LLB graph this RUN would generate 2 different container exec steps).

    Same as above:

    FROM --platform=amd64 alpine AS base
    RUN cmd
    
    FROM --platform=arm64 alpine AS base-for-stage1
    RUN cmd
    
    FROM base-for-stage1
    COPY --from=base /foo /bar
    RUN smth
    

    So the answer to your question depends on how much a change like that would affect the codebase and if it would increase the complexity of generating LLB graph a lot. If not then I would be ok with it. Otherwise we can add a warning to let user know that --platform only affects resolving multi-platform images.

  8. jberkenbilt commented on Mar 4, 2025

    @jberkenbilt

    In the interim, maybe the docs on FROM should be modified to mention this restriction, possibly with a link to this issue. If that's done, the issue should also include a reminder to update the docs. Before reporting docker/buildx#3034, I spent a few hours on this, and although I tried to find an existing issue, I didn't find this issue because it wasn't obvious to me that it belonged in this project. It would be nice to understand exactly what's happening with ARG and FROM and --platform. There's a section on the docs about it, but it's not that detailed. Here's a suggestion of would I would have found helpful in the docs. I'm describing it here for now rather than as a PR because I'm not sure my understanding is correct. If not, a corrected version of my understanding would be useful. I don't know anything about the internals; for example, I don't know what the LLB graph is, though I can guess because I understand that multi-platform builds create a fork in the graph of layers.

    In FROM, it would be nice to see something like

    FROM <previous-stage> [AS <name>]
    

    as one of the choices, particularly since the link for base image doesn't contain any content.

    In the bulleted list, I'd add something like this:

    • The --platform flag is ignored when <image> is a prior build stage rather than an external image.

    Somewhere, there needs to be an explanation of how layers work with multi-platform builds. I don't know whether it belongs here or in some other doc, but if some other doc, there should be a link here, either in FROM, or Understand how ARG and FROM interact, or both. Perhaps there should be a link to https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide/. Here are a few points that would be useful to call out explicitly. The diagrams in your blog post above make this clear.

    • When you use FROM --platform=<platform> with a multi-platform builds, a version of the base image for <platform> is used for all steps on all targets, and the layers created by the steps can be parents of layers for an image for the other platforms.
    • As soon as you use an ARG command that includes one of the automatic TARGET* variables, the layer tree splits. The result is that subsequent layers for all target platforms that differ from the build platform will be from a base image of a different platform. As such, these images are not runnable (without emulation) from their target platform, and cross-compiled targets need to be copied (using COPY --from=...) to a new stage that originated from a multi-platform stage. (This is done in all the examples, and the diagrams show it, but I don't know that it's ever spelled out. I'm also not sure whether there are other things besides ARG TARGET* that causes the layer tree to "split". I didn't find anything that exactly specifies what causes the split.)
    • Once a build stage as "split" into separate layers for different target platforms, you can no longer use a layer from a different platform as a build step. This is the fundamental issue here. If you are building with --platform=linux/arm64,linux/amd64 so you have (and here I'm looking at the image whose caption is "Sample Dockerfile commands as run on Apple M1 machine. Blue contains x86 binaries, yellow ARM." in the blog):
    FROM --platform=linux/arm64 alpine AS build
    RUN apk add mycompiler
    COPY src src
    ARG TARGETPLATFORM (causes split, not explicitly shown in diagram)
      /              \
    yellow      yellow
    <end of stage build> (not explicitly marked)
    FROM alpine (creates new stage separate trees for each platform)
    (the diagram makes it look like the `FROM ALPINE` steps are descended from the steps above it, but the are not actually.)
    yellow      blue
    yellow      blue
    

    If you create a new stage continuing from build (after the layer tree has already split), the new stage will already be split, even if --from=<platform> is specified. (This is where several of us are saying the behavior should change or there should be a warning/error.)

    FROM build AS build2  # --platform here would be ignored
    RUN do something
    yellow         yellow-with-blue-stuff [emulated]
    

    In this situation, do something will be run for linux/amd64 using the arm64 image that also has x86 binaries on that were previously created. (This is where we were expecting the yellow side to be the single layer.) This would result in arm binaries being run in an emulated x86 layer on an arm system. I'm not sure what this actually does....but it seems empirically that it's using two layers of emulation. Anyway, for my build, it worked, but it was very slow. It's hard to imagine that this is ever desirable behavior.

    So, in summary, here's what's missing from the docs:

    • --platform=... is ignored in FROM --platform=... <image> when <image> is a previous stage. This should possibly be accompanied by a link to this issue until either it becomes an error or the behavior is changed.
    • Using ARG TARGET* splits the layer tree for a stage started with FROM --platform=... into separate layers for each target platform. (This is clear in the diagrams in the blog, but it appears to be omitted from the docs on FROM and FROM/ARG interaction. If there are other things that cause the split, state them.)
    • When you create a new stage from a previous stage, if the previous stage's layer tree has already split, either because it started off multi-platform or because an ARG TARGET* (or anything else) command caused it to split, the new stage will be multi-platform. You can't create a new stage with a sequence of layers from only one of the platforms. (If this is deemed to be a bug, there should be a link to this issue, and that item should be clarified with an example when the bug is fixed.)
  9. bpaquet commented on Jun 18, 2025

    @bpaquet
    Contributor

    I'm also hit by this issue. The only workaround I have is to split the build in 2 and push the intermediate stage into a registry, launch a second build reusing it. This is far away of being ideal.

    On the other side, may be we can fix the issue for a local multi arch build, but if we have multiple remote builder, it's a way more complicated.

    @tonistiigi Is it an option to add a --remote-image in the FROM to ask buildkit to push a temporary image somewhere?

  10. tonistiigi commented on Jun 18, 2025

    @tonistiigi
    Member

    Is it an option to add a --remote-image in the FROM to ask buildkit to push a temporary image somewhere?

    No, if we would fix it we would just make --platform reevaluate the parent stage as well as described in #4929 (comment) .

    In your example case, you just need to move the --platform to the FROM debian:bookworm-slim stage, assuming you don't use that stage for anything else.

  11. Fotoblysk commented on Nov 2, 2025

    @Fotoblysk

    I'm also facing this issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions