Cross-Platform Building
Quick Start
Section titled “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:dockerThen build for any platform:
# Build for current platform (production by default)wails3 build
# Build for specific platformswails3 build GOOS=windowswails3 build GOOS=darwinwails3 build GOOS=linux
# Build for ARM64 architecturewails3 build GOOS=windows GOARCH=arm64wails3 build GOOS=darwin GOARCH=arm64wails3 build GOOS=linux GOARCH=arm64
# Environment variable style also worksGOOS=darwin GOARCH=arm64 wails3 buildWindows
Section titled “Windows”Windows is the simplest cross-compilation target because it doesn’t require CGO by default.
wails3 build GOOS=windowsThis 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 setupwails3 task setup:docker
# Build with CGO enabledwails3 task windows:build CGO_ENABLED=1The Taskfile detects CGO_ENABLED=1 on non-Windows hosts and automatically uses the Docker image.
macOS builds require CGO for WebView integration, which means cross-compilation needs special tooling.
# Build for Apple Silicon (arm64) - defaultwails3 build GOOS=darwin
# Build for Intel (amd64)wails3 build GOOS=darwin GOARCH=amd64
# Build universal binary (both architectures)wails3 task darwin:build:universalFrom Linux or Windows, you’ll need to set up Docker first:
wails3 task setup:dockerOnce 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 builds require CGO for WebView integration.
wails3 build GOOS=linux
# Build for specific architecturewails3 build GOOS=linux GOARCH=amd64wails3 build GOOS=linux GOARCH=arm64From macOS or Windows, you’ll need to set up Docker first:
wails3 task setup:dockerThe 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
Section titled “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=amd64The 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
Section titled “How It Works”Cross-Compilation Matrix
Section titled “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
Section titled “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
Section titled “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
Section titled “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:dockerYou can check if the image is ready by running wails3 doctor.
macOS SDK
Section titled “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
Section titled “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
Section titled “Dockerfile”Create a Dockerfile with the following content:
# syntax=docker/dockerfile:1FROM golang:1.24-alpine
ARG ZIG_VERSION=0.14.0ARG MACOS_SDK_VERSION=14.5ARG 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 arm64COPY <<'ZIGWRAP' /usr/local/bin/zcc-darwin-arm64#!/bin/shARGS=""SKIP_NEXT=0for 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" ;; esacdoneexec 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 $ARGSZIGWRAPRUN chmod +x /usr/local/bin/zcc-darwin-arm64
# Darwin amd64COPY <<'ZIGWRAP' /usr/local/bin/zcc-darwin-amd64#!/bin/shARGS=""SKIP_NEXT=0for 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" ;; esacdoneexec 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 $ARGSZIGWRAPRUN chmod +x /usr/local/bin/zcc-darwin-amd64
# Linux amd64COPY <<'ZIGWRAP' /usr/local/bin/zcc-linux-amd64#!/bin/shARGS=""SKIP_NEXT=0for arg in "$@"; do if [ $SKIP_NEXT -eq 1 ]; then SKIP_NEXT=0 continue fi case "$arg" in -target) SKIP_NEXT=1 ;; *) ARGS="$ARGS $arg" ;; esacdoneexec zig cc -target x86_64-linux-musl $ARGSZIGWRAPRUN chmod +x /usr/local/bin/zcc-linux-amd64
# Linux arm64COPY <<'ZIGWRAP' /usr/local/bin/zcc-linux-arm64#!/bin/shARGS=""SKIP_NEXT=0for arg in "$@"; do if [ $SKIP_NEXT -eq 1 ]; then SKIP_NEXT=0 continue fi case "$arg" in -target) SKIP_NEXT=1 ;; *) ARGS="$ARGS $arg" ;; esacdoneexec zig cc -target aarch64-linux-musl $ARGSZIGWRAPRUN chmod +x /usr/local/bin/zcc-linux-arm64
# Windows amd64COPY <<'ZIGWRAP' /usr/local/bin/zcc-windows-amd64#!/bin/shARGS=""SKIP_NEXT=0for 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" ;; esacdoneexec zig cc -target x86_64-windows-gnu $ARGSZIGWRAPRUN chmod +x /usr/local/bin/zcc-windows-amd64
# Windows arm64COPY <<'ZIGWRAP' /usr/local/bin/zcc-windows-arm64#!/bin/shARGS=""SKIP_NEXT=0for 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" ;; esacdoneexec zig cc -target aarch64-windows-gnu $ARGSZIGWRAPRUN chmod +x /usr/local/bin/zcc-windows-arm64
# Build scriptCOPY <<'SCRIPT' /usr/local/bin/build.sh#!/bin/shset -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=1export 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
# BuildAPP=${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}"SCRIPTRUN chmod +x /usr/local/bin/build.sh
WORKDIR /appENTRYPOINT ["/usr/local/bin/build.sh"]CMD ["darwin", "arm64"]Building the Image
Section titled “Building the Image”Save the Dockerfile and build the image:
docker build -t wails-cross .Using a Different SDK Version
Section titled “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
Section titled “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.xzRUN 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
Section titled “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
Section titled “Troubleshooting”Docker image not found
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “Universal binary creation”Universal binaries (arm64 + amd64 combined) can be built on any platform:
wails3 task darwin:build:universalOn 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
Section titled “Next Steps”- Building Applications - Basic build commands and options
- Signing Applications - Code signing for distribution