Cross-Platform Building
Quick Start
Wails v3 supports building for Windows, macOS, and Linux from any host operating system. The build system automatically detects your environment and chooses the right compilation method.
Want to cross-compile to macOS and Linux? Run this once to set up the Docker images (~800MB download):
wails3 task setup:docker
Then build for any platform:
# Build for current platform (production by default)
wails3 build
# Build for specific platforms
wails3 build GOOS=windows
wails3 build GOOS=darwin
wails3 build GOOS=linux
# Build for ARM64 architecture
wails3 build GOOS=windows GOARCH=arm64
wails3 build GOOS=darwin GOARCH=arm64
wails3 build GOOS=linux GOARCH=arm64
# Environment variable style also works
GOOS=darwin GOARCH=arm64 wails3 build
Windows
Windows is the simplest cross-compilation target because it doesn’t require CGO by default.
wails3 build GOOS=windows
This works from any host OS with no additional setup. Go’s built-in cross-compilation handles everything.
If your app requires CGO (e.g., you’re using a C library or CGO-dependent package), you’ll need Docker when building from macOS or Linux:
# One-time setup
wails3 task setup:docker
# Build with CGO enabled
wails3 task windows:build CGO_ENABLED=1
The Taskfile detects CGO_ENABLED=1 on non-Windows hosts and automatically uses the Docker image.
macOS
macOS builds require CGO for WebView integration, which means cross-compilation needs special tooling.
# Build for Apple Silicon (arm64) - default
wails3 build GOOS=darwin
# Build for Intel (amd64)
wails3 build GOOS=darwin GOARCH=amd64
# Build universal binary (both architectures)
wails3 task darwin:build:universal
From Linux or Windows, you’ll need to set up Docker first:
wails3 task setup:docker
Once the images are built, the build system detects that you’re not on macOS and uses Docker automatically. You don’t need to change your build commands.
Note that cross-compiled macOS binaries are not code-signed. You’ll need to sign them on macOS or in CI before distribution.
Linux
Linux builds require CGO for WebView integration.
wails3 build GOOS=linux
# Build for specific architecture
wails3 build GOOS=linux GOARCH=amd64
wails3 build GOOS=linux GOARCH=arm64
From macOS or Windows, you’ll need to set up Docker first:
wails3 task setup:docker
The build system detects that you’re not on Linux and uses Docker automatically.
On Linux without a C compiler, the build system checks for gcc or clang. If neither is found, it falls back to Docker. This is useful for minimal containers or systems without build tools installed. You can either:
- Install a C compiler:
sudo apt install build-essential(Debian/Ubuntu) orsudo pacman -S base-devel(Arch) - Build the Docker image and let it be used automatically
ARM Architecture
All platforms support ARM64 cross-compilation using GOARCH:
# Windows ARM64 (Surface Pro X, Windows on ARM)
wails3 build GOOS=windows GOARCH=arm64
# Linux ARM64 (Raspberry Pi 4/5, AWS Graviton)
wails3 build GOOS=linux GOARCH=arm64
# macOS ARM64 (Apple Silicon - this is the default on macOS)
wails3 build GOOS=darwin GOARCH=arm64
# macOS Intel (amd64)
wails3 build GOOS=darwin GOARCH=amd64
The Docker image includes Zig cross-compiler targets for both amd64 and arm64 on all platforms, so ARM builds work from any host:
| Build ARM64 for | From Windows | From macOS | From Linux |
|---|---|---|---|
| Windows ARM64 | Native Go | Native Go | Native Go |
| macOS ARM64 | Docker | Native | Docker |
| Linux ARM64 | Docker | Docker | Docker* |
*Linux ARM64 from Linux x86_64 uses Docker because CGO cross-compilation requires a different toolchain.
How It Works
Cross-Compilation Matrix
| Host → Target | Windows | macOS | Linux |
|---|---|---|---|
| Windows | Native | Docker | Docker |
| macOS | Native Go | Native | Docker |
| Linux | Native Go | Docker | Native |
- Native = Platform’s native toolchain, no additional setup
- Native Go = Go’s built-in cross-compilation (
CGO_ENABLED=0) - Docker = Docker image with Zig cross-compiler
CGO Requirements
| Target | CGO Required | Cross-Compilation Method |
|---|---|---|
| Windows | No (default) | Native Go. Docker only if CGO_ENABLED=1 |
| macOS | Yes | Docker with macOS SDK |
| Linux | Yes | Docker, or native if C compiler available |
Auto-Detection
The Taskfiles automatically choose the right build method based on your environment:
- Windows target: Uses native Go cross-compilation by default. If you explicitly set
CGO_ENABLED=1on a non-Windows host, it switches to Docker. - macOS target: Uses Docker automatically when not on macOS. No manual intervention needed.
- Linux target: Checks for
gccorclang. Uses native compilation if found, otherwise falls back to Docker.
Docker Image
Wails uses a single Docker image (wails-cross) that can build for all platforms. It uses Zig as the cross-compiler, which can target any platform from any host. The macOS SDK is included for darwin targets.
wails3 task setup:docker
You can check if the image is ready by running wails3 doctor.
macOS SDK
The Docker image downloads the macOS SDK from wailsapp/macosx-sdks during the image build process. This is required because macOS headers are needed for CGO compilation.
Important: Wails does not distribute the macOS SDK. Users are responsible for reviewing Apple’s SDK license terms before using this feature.
Build Your Own Image
If you need to customize the Docker image (e.g., use a different macOS SDK version, add additional tools, or use your own SDK), you can build the image yourself.
Dockerfile
Create a Dockerfile with the following content:
# syntax=docker/dockerfile:1
FROM golang:1.24-alpine
ARG ZIG_VERSION=0.14.0
ARG MACOS_SDK_VERSION=14.5
ARG IMAGE_VERSION=1.0.0
LABEL org.opencontainers.image.title="Wails Cross-Compiler"
LABEL org.opencontainers.image.description="Cross-compile Wails v3 apps to macOS, Linux, and Windows"
LABEL org.opencontainers.image.source="https://github.com/wailsapp/wails"
LABEL org.opencontainers.image.vendor="Wails"
LABEL org.opencontainers.image.version="${IMAGE_VERSION}"
LABEL io.wails.sdk.version="${MACOS_SDK_VERSION}"
LABEL io.wails.zig.version="${ZIG_VERSION}"
RUN apk add --no-cache curl xz nodejs npm gcompat
RUN curl -L "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz" \
| tar -xJ -C /opt \
&& ln -s /opt/zig-linux-x86_64-${ZIG_VERSION}/zig /usr/local/bin/zig
RUN curl -fL --retry 3 --retry-delay 5 -o /tmp/sdk.tar.xz \
"https://github.com/wailsapp/macosx-sdks/releases/download/${MACOS_SDK_VERSION}/MacOSX${MACOS_SDK_VERSION}.sdk.tar.xz" \
&& tar -xJf /tmp/sdk.tar.xz -C /opt \
&& mv /opt/MacOSX${MACOS_SDK_VERSION}.sdk /opt/macos-sdk \
&& rm /tmp/sdk.tar.xz
ENV MACOS_SDK_PATH=/opt/macos-sdk
# Create zig cc wrappers for each target
# Darwin arm64
COPY <<'ZIGWRAP' /usr/local/bin/zcc-darwin-arm64
#!/bin/sh
ARGS=""
SKIP_NEXT=0
for arg in "$@"; do
if [ $SKIP_NEXT -eq 1 ]; then
SKIP_NEXT=0
continue
fi
case "$arg" in
-target) SKIP_NEXT=1 ;;
-mmacosx-version-min=*) ;;
*) ARGS="$ARGS $arg" ;;
esac
done
exec zig cc -target aarch64-macos-none -isysroot /opt/macos-sdk -I/opt/macos-sdk/usr/include -L/opt/macos-sdk/usr/lib -F/opt/macos-sdk/System/Library/Frameworks -w $ARGS
ZIGWRAP
RUN chmod +x /usr/local/bin/zcc-darwin-arm64
# Darwin amd64
COPY <<'ZIGWRAP' /usr/local/bin/zcc-darwin-amd64
#!/bin/sh
ARGS=""
SKIP_NEXT=0
for arg in "$@"; do
if [ $SKIP_NEXT -eq 1 ]; then
SKIP_NEXT=0
continue
fi
case "$arg" in
-target) SKIP_NEXT=1 ;;
-mmacosx-version-min=*) ;;
*) ARGS="$ARGS $arg" ;;
esac
done
exec zig cc -target x86_64-macos-none -isysroot /opt/macos-sdk -I/opt/macos-sdk/usr/include -L/opt/macos-sdk/usr/lib -F/opt/macos-sdk/System/Library/Frameworks -w $ARGS
ZIGWRAP
RUN chmod +x /usr/local/bin/zcc-darwin-amd64
# Linux amd64
COPY <<'ZIGWRAP' /usr/local/bin/zcc-linux-amd64
#!/bin/sh
ARGS=""
SKIP_NEXT=0
for arg in "$@"; do
if [ $SKIP_NEXT -eq 1 ]; then
SKIP_NEXT=0
continue
fi
case "$arg" in
-target) SKIP_NEXT=1 ;;
*) ARGS="$ARGS $arg" ;;
esac
done
exec zig cc -target x86_64-linux-musl $ARGS
ZIGWRAP
RUN chmod +x /usr/local/bin/zcc-linux-amd64
# Linux arm64
COPY <<'ZIGWRAP' /usr/local/bin/zcc-linux-arm64
#!/bin/sh
ARGS=""
SKIP_NEXT=0
for arg in "$@"; do
if [ $SKIP_NEXT -eq 1 ]; then
SKIP_NEXT=0
continue
fi
case "$arg" in
-target) SKIP_NEXT=1 ;;
*) ARGS="$ARGS $arg" ;;
esac
done
exec zig cc -target aarch64-linux-musl $ARGS
ZIGWRAP
RUN chmod +x /usr/local/bin/zcc-linux-arm64
# Windows amd64
COPY <<'ZIGWRAP' /usr/local/bin/zcc-windows-amd64
#!/bin/sh
ARGS=""
SKIP_NEXT=0
for arg in "$@"; do
if [ $SKIP_NEXT -eq 1 ]; then
SKIP_NEXT=0
continue
fi
case "$arg" in
-target) SKIP_NEXT=1 ;;
-Wl,*) ;;
*) ARGS="$ARGS $arg" ;;
esac
done
exec zig cc -target x86_64-windows-gnu $ARGS
ZIGWRAP
RUN chmod +x /usr/local/bin/zcc-windows-amd64
# Windows arm64
COPY <<'ZIGWRAP' /usr/local/bin/zcc-windows-arm64
#!/bin/sh
ARGS=""
SKIP_NEXT=0
for arg in "$@"; do
if [ $SKIP_NEXT -eq 1 ]; then
SKIP_NEXT=0
continue
fi
case "$arg" in
-target) SKIP_NEXT=1 ;;
-Wl,*) ;;
*) ARGS="$ARGS $arg" ;;
esac
done
exec zig cc -target aarch64-windows-gnu $ARGS
ZIGWRAP
RUN chmod +x /usr/local/bin/zcc-windows-arm64
# Build script
COPY <<'SCRIPT' /usr/local/bin/build.sh
#!/bin/sh
set -e
OS=${1:-darwin}
ARCH=${2:-arm64}
case "${OS}-${ARCH}" in
darwin-arm64|darwin-aarch64) export CC=zcc-darwin-arm64; export GOARCH=arm64; export GOOS=darwin ;;
darwin-amd64|darwin-x86_64) export CC=zcc-darwin-amd64; export GOARCH=amd64; export GOOS=darwin ;;
linux-arm64|linux-aarch64) export CC=zcc-linux-arm64; export GOARCH=arm64; export GOOS=linux ;;
linux-amd64|linux-x86_64) export CC=zcc-linux-amd64; export GOARCH=amd64; export GOOS=linux ;;
windows-arm64|windows-aarch64) export CC=zcc-windows-arm64; export GOARCH=arm64; export GOOS=windows ;;
windows-amd64|windows-x86_64) export CC=zcc-windows-amd64; export GOARCH=amd64; export GOOS=windows ;;
*) echo "Usage: <os> <arch>"; echo " os: darwin, linux, windows"; echo " arch: amd64, arm64"; exit 1 ;;
esac
export CGO_ENABLED=1
export CGO_CFLAGS="-w"
# Build frontend if exists and not already built (host may have built it)
if [ -d "frontend" ] && [ -f "frontend/package.json" ] && [ ! -d "frontend/dist" ]; then
(cd frontend && npm install --silent && npm run build --silent)
fi
# Build
APP=${APP_NAME:-$(basename $(pwd))}
mkdir -p bin
EXT=""
LDFLAGS="-s -w"
if [ "$GOOS" = "windows" ]; then
EXT=".exe"
LDFLAGS="-s -w -H windowsgui"
fi
go build -ldflags="$LDFLAGS" -o bin/${APP}-${GOOS}-${GOARCH}${EXT} .
echo "Built: bin/${APP}-${GOOS}-${GOARCH}${EXT}"
SCRIPT
RUN chmod +x /usr/local/bin/build.sh
WORKDIR /app
ENTRYPOINT ["/usr/local/bin/build.sh"]
CMD ["darwin", "arm64"]Building the Image
Save the Dockerfile and build the image:
docker build -t wails-cross .
Using a Different SDK Version
Change the MACOS_SDK_VERSION build argument:
docker build -t wails-cross --build-arg MACOS_SDK_VERSION=15.0 .
See available SDK versions for options.
Using Your Own SDK
If you have your own macOS SDK (e.g., extracted from Xcode), you can modify the Dockerfile to use a local file instead of downloading:
# Replace the curl/tar SDK download section with:
# (Replace 14.5 with your SDK version in both the COPY and mv commands)
COPY MacOSX14.5.sdk.tar.xz /tmp/sdk.tar.xz
RUN tar -xJf /tmp/sdk.tar.xz -C /opt \
&& mv /opt/MacOSX14.5.sdk /opt/macos-sdk \
&& rm /tmp/sdk.tar.xzPlace your SDK tarball in the same directory as the Dockerfile and build.
CI/CD Integration
For production releases, we recommend using CI/CD with native runners for each platform. This avoids cross-compilation entirely and ensures you get properly signed binaries.
name: Build
on:
push:
branches: [main]
jobs:
build:
strategy:
matrix:
include:
- os: ubuntu-latest
goos: linux
- os: macos-latest
goos: darwin
- os: windows-latest
goos: windows
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.24'
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Wails CLI
run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest
- name: Install Task
uses: arduino/setup-task@v2
- name: Build
run: wails3 build
- uses: actions/upload-artifact@v4
with:
name: app-${{ matrix.goos }}
path: bin/Troubleshooting
Docker image not found
Docker image 'wails-cross' not found.Run wails3 task setup:docker to build the Docker image. You only need to do this once.
Docker daemon not running
Docker is required for cross-compilation. Please install Docker.Start Docker Desktop or the Docker daemon. On Linux, you may need to run sudo systemctl start docker.
No C compiler on Linux
If you see CGO-related errors when building on Linux, you have two options:
-
Install a C compiler:
- Debian/Ubuntu:
sudo apt install build-essential - Arch Linux:
sudo pacman -S base-devel - Fedora:
sudo dnf install gcc
- Debian/Ubuntu:
-
Use Docker instead: Run
wails3 task setup:dockerand the Taskfile will use it automatically when no compiler is detected.
macOS binaries not signed
Cross-compiled macOS binaries are not code-signed. Apple requires code signing for distribution, so you’ll need to:
- Sign the binary on a macOS machine, or
- Sign in CI using a macOS runner
See Signing Applications for details.
Universal binary creation
Universal binaries (arm64 + amd64 combined) can be built on any platform:
wails3 task darwin:build:universal
On Linux and Windows, Wails uses its built-in wails3 tool lipo command (powered by konoui/lipo) to combine the binaries. This creates a single binary that runs natively on both Apple Silicon and Intel Macs.
Next Steps
- Building Applications - Basic build commands and options
- Signing Applications - Code signing for distribution