xx removes the architecture conditionals from your Dockerfile
Dockerfile cross-compilation helpers
At a glance
- What is it?
- xx is a set of POSIX shell helpers distributed as a Docker image that read the TARGET* build arguments and set up cross-compilers, so a multi-platform Dockerfile can be written once without per-architecture branches. It suits anyone shipping multi-arch images, and it expects Alpine or Debian based build stages.
- Who is it for?
- Adopt xx if you publish multi-architecture images and your Dockerfiles currently carry per-architecture conditionals, because the helpers turn the TARGETPLATFORM argument into a configured toolchain and a package manager for the target, with native performance for the build machine.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Shell, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem xx removes from your Dockerfile
xx provides tools to support cross-compilation from Dockerfiles that understand the platform flag passed in from docker build or docker buildx build. The stated goal is that these helpers allow you to build multi-platform images from any architecture into any architecture supported by your compiler with native performance, and that adding xx to your Dockerfile should only need minimal updates and should not require custom conditions for specific architectures. That last clause is the actual value. The classic multi-arch Dockerfile is a thicket of ARG TARGETPLATFORM, per-architecture RUN branches, and a different package manager invocation for each target family, and it usually breaks when a new architecture is added to your matrix. The primer in the documentation explains the underlying mechanism, which is that cross-compilation is achieved in Dockerfiles by using multi-stage builds and defining some of the stages to always run on the native architecture used by the builder while executing the cross-compiling compiler. A Dockerfile stage started with the FROM keyword defaults to the target architecture, but that can be overridden with a FROM platform flag, and using the automatic platform arguments in global scope the cross-compiler stage can be pinned to the build platform while the target platform is passed to the compiler through an environment variable.
The multi-stage pattern the helpers assume
The primer shows the shape the helpers are designed around, with a build stage pinned to the builder architecture and a final stage that takes the target architecture by default.
FROM --platform=$BUILDPLATFORM alpine AS xbuild
ARG TARGETPLATFORM
RUN ./compile --target=$TARGETPLATFORM -o /out/myapp
FROM alpine
COPY --from=xbuild /out/myapp /binAfter compilation, the resulting assets can be copied into another stage that will become the result of the build, and usually that stage does not use the platform flag so that every stage is based on the expected target architecture. The mechanism xx plugs into is therefore not a trick of its own. It is the standard multi-stage pattern, and xx simply removes the conditional logic that pattern usually drags along with it. The repository layout is consistent with a small project of this kind: a src/ directory, a util/ directory, a hack/ directory, a docker-bake.hcl for building the image itself, a MAINTAINERS file and a LICENSE. The primary language is Shell, and the licence is MIT, with the last push to the repository on 2026-10-16 and the most recent release, v1.10.0, dated 2026-10-12.
Adding xx to a Dockerfile
xx is distributed with a Docker image, tonistiigi/xx, that contains a collection of helper scripts reading TARGET* environment variables to automatically configure the compilation targets. The scripts are based on Posix Shell, so they should work on top of any image, but currently xx is expected to work on Alpine and Debian/Ubuntu based distros. In order to avoid unexpected changes, the documentation suggests pinning the image using an immutable digest, and it notes that although xx only contains shell scripts that are identical for every platform, it is recommended to also import xx with the platform flag so that the import commands are shared for all compilation targets.
FROM --platform=$BUILDPLATFORM tonistiigi/xx AS xx
FROM --platform=$BUILDPLATFORM alpine
# copy xx scripts to your build stage
COPY --from=xx / /
# export TARGETPLATFORM (or other TARGET*)
ARG TARGETPLATFORM
# you can now call xx-* commands
RUN xx-info envThe copy step pulls the whole helper tree into the build stage with COPY from xx and a slash destination, and the ARG declaration is what makes the target platform visible to the scripts. The command list in the current release is xx-info, xx-apk, xx-apt-get, xx-cc, xx-c++, xx-clang, xx-clang++, xx-go, xx-cargo and xx-verify. One of them does something extra: xx-clang and its aliases create additional aliases, for example a target-triple prefixed clang and pkg-config, on first invocation or on an xx-clang setup-target-triple call.
xx-info and the formats it normalises
The xx-info command returns normalized information about the current build context, and the documentation explains the value: it allows you to get various information about your build target and configuration and avoid the need for converting from one format to another. This is the helper most Dockerfile authors will use most, because it is the one that removes string parsing from build scripts. The documentation's table of contents lists the pieces it handles, starting with parsing the current target, then architecture formats, then the target triple, then the build context. In practice this means your RUN lines can ask the helper what the target is rather than testing strings yourself, which is the difference between a Dockerfile that needs editing for each new architecture and one that does not. The xx-apk, xx-apt and xx-apt-get family covers the other half of the problem, installing packages for the target architecture rather than for the machine doing the build, so a multi-arch stage can pull in the right shared libraries and toolchain packages without branching on the target.
Supported targets, and the language-specific gaps
The supported target list is explicit, and the gaps in it are the most useful thing to know before you plan an adoption. xx supports building from and into Linux amd64, arm64, arm/v7, s390x, ppc64le and 386, and Alpine, Debian and Ubuntu. Risc-V is supported for Go and Rust builds and for newer distros that provide Risc-V packages like alpine:edge or debian:sid. Go builds that don't depend on system packages can additionally target MacOS and Windows on all architectures, while C, C++, CGo and Rust builds are supported for MacOS targets when an external SDK image is provided. That last clause matters, because cross-compiling to MacOS from Linux is otherwise not something a tool can conjure. There is also a partial-support note that is easy to skim past: the xx-info command also works on RHEL-style distros, but no support is provided for the package manager wrappers, for example yum and dnf, there. So on a RHEL host you can inspect the build context but you cannot use the helpers to install target packages, which is a real limit for anyone whose CI runs on Red Hat based images.
Verifying that the helper image has not been tampered with
Because xx sits in the build path of every image you publish, the project takes release integrity seriously, and the mechanism is worth understanding rather than skipping. Latest xx releases are built and signed using Docker Github Builder, and you can verify the authenticity of the release with a Rego policy in the latest Docker Buildx. The policy is short.
is_xx if input.image.repo == "tonistiigi/xx"
is_xx_valid if {
is_xx
docker_github_builder_tag(input.image, input.image.repo, sprintf("v%s", [input.image.tag]))
}There is a fallback for older releases from before signatures were introduced, using a shortlist of previous versions with their checksums, and the documentation states that xx version tags are always guaranteed to be immutable. A separate policy clause covers master branch builds, matching the tag and checking against the builder directly. The practical pairing with the earlier installation advice is clear. If you pin the image by digest because you want to avoid unexpected changes, you can also verify at build time that the image you pulled is the one the project published, which is a stronger guarantee than either measure alone.
Where xx is the wrong tool
The boundaries follow from what the helpers assume. If you ship one architecture only, the whole arrangement is overhead: a second build stage, an extra image pull and a set of environment variables that buy you nothing. If your build stages are not Alpine or Debian based, the package manager wrappers do not apply, and while the scripts are POSIX shell and should work on top of any image, the xx-info command is the only part with a stated RHEL story. There is also a cost in the shared stage, because COPY from xx and the platform flag on the import add a dependency on an image you now have to keep pinned and, if you care, verify. The other limitation is granularity. The helpers are built around a target triple and a package manager, so a toolchain that needs something more specific than a compiler and a package list, a code generator with its own dependencies, or a language runtime that wants a prebuilt toolchain for the target rather than a cross compiler, will not be fully covered by the xx-cc and xx-cargo style wrappers. A plain multi-stage Dockerfile with a per-architecture branch, or a language with first-class cross-compilation support such as Go, remains a legitimate choice for a project with one or two targets.
Editorial conclusion
Adopt xx if you publish multi-architecture images and your Dockerfiles currently carry per-architecture conditionals, because the helpers turn the TARGETPLATFORM argument into a configured toolchain and a package manager for the target, with native performance for the build machine. Do not adopt it if you build a single-architecture image, where the added stage is pure overhead, or if your build stages are not Alpine or Debian based, because the package manager wrappers cover only those. Verify two things before you convert a Dockerfile: that your build stage can be switched to run on the native architecture using the BUILDPLATFORM argument, since the whole scheme depends on a cross-compiling stage, and that the compiler you need is available for the target, since the Clang aliases are created on first invocation and target support is not uniform across languages. The practical first step is to add the xx stage to a scratch branch of your Dockerfile and run xx-info env to see the normalized target the helper derives.
Frequently asked questions
What is the xx Docker image?
It is a Docker image, tonistiigi/xx, containing POSIX shell helper scripts that read TARGET* environment variables to automatically configure compilation targets for the architecture you are building for. The helpers are expected to work on Alpine and Debian/Ubuntu based distros.
How do I add xx to a Dockerfile?
Import the image with FROM --platform=$BUILDPLATFORM tonistiigi/xx AS xx, then in your build stage use COPY --from=xx / / to copy the scripts, declare ARG TARGETPLATFORM, and call the xx-* commands such as xx-info env. The documentation recommends pinning the image by digest and importing it with the platform flag.
Which commands does xx provide?
The current release contains xx-info, xx-apk, xx-apt-get, xx-cc, xx-c++, xx-clang, xx-clang++, xx-go, xx-cargo and xx-verify. The xx-clang command and its aliases create extra aliases such as the target-triple prefixed clang and pkg-config on first invocation.
Which architectures and operating systems does xx support?
Linux amd64, arm64, arm/v7, s390x, ppc64le and 386, with Alpine, Debian and Ubuntu, and Risc-V for Go and Rust builds and for newer distros. Go builds that do not depend on system packages can also target MacOS and Windows on all architectures.
How can I verify that the xx image is authentic?
Recent releases are built and signed using Docker Github Builder, and the documentation provides a Rego policy for the latest Docker Buildx that checks the image repository and the builder tag. Older releases can be checked against a shortlist of checksums, and xx version tags are guaranteed to be immutable.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/tonistiigi-xx)Was this useful?