Кросс-компиляция в Go: собираем бинарник под Windows и Linux
Go умеет собирать исполняемые файлы под чужую ОС одной командой — без виртуальных машин и Docker. Разбираем, как настроить переменные окружения, получить .exe на Linux-машине и ELF-бинарник на Windows, и что делать, когда CGO ломает весь план.

Что нужно до начала
Кросс-компиляция в Go работает «из коробки» начиная с версии 1.5. Вам понадобится:
- Go 1.21 или новее (проверьте командой
go version); - исходный код проекта, который компилируется без ошибок под вашу текущую ОС (
go build ./...проходит чисто); - терминал — bash, zsh, PowerShell или cmd.
Никаких дополнительных тулчейнов, SDK и кросс-компиляторов для чистого Go-кода не требуется. Весь процесс занимает пять минут, а результат — готовый бинарник, который можно перенести на целевую машину и запустить без установки Go.
Шаг 1. Проверяем, что проект собирается локально
Прежде чем менять целевую платформу, убедитесь, что обычная сборка проходит без ошибок:
# Go 1.21+
go build -o myapp ./cmd/myapp
Если команда завершилась без вывода и в текущей директории появился файл myapp (или myapp.exe на Windows) — всё готово. Если посыпались ошибки, исправьте их до перехода к кросс-компиляции: чужая платформа добавит новых, а не уберёт старые.
Шаг 2. Выставляем GOOS и GOARCH
Go-компилятор выбирает целевую ОС и архитектуру по двум переменным окружения: GOOS и GOARCH. Их достаточно задать перед вызовом go build.
Собираем .exe на Linux или macOS
# Сборка Windows-бинарника из-под Linux/macOS
GOOS=windows GOARCH=amd64 go build -o myapp.exe ./cmd/myapp
Проверка: в текущей директории появился файл myapp.exe. Его можно скопировать на Windows-машину и запустить двойным кликом или из PowerShell.
Собираем Linux-бинарник на Windows (PowerShell)
# PowerShell — переменные задаются через $env:
$env:GOOS = "linux"
$env:GOARCH = "amd64"
go build -o myapp ./cmd/myapp
# Не забудьте вернуть значения после сборки:
Remove-Item Env:GOOS
Remove-Item Env:GOARCH
Проверка: файл myapp без расширения. Перенесите его на Linux-сервер, выставьте права chmod +x myapp и запустите.
Частая ошибка — забыть убрать переменные после кросс-сборки. Следующийgo buildилиgo testмолча соберётся под чужую ОС, тесты не запустятся, и вы потратите полчаса на поиск причины.
Шаг 3. Популярные комбинации GOOS/GOARCH
| Цель | GOOS | GOARCH | Имя файла (условно) |
|---|---|---|---|
| Windows x86-64 | windows | amd64 | myapp.exe |
| Linux x86-64 | linux | amd64 | myapp |
| Linux ARM64 (серверы Graviton, Raspberry Pi 4) | linux | arm64 | myapp |
| macOS Apple Silicon | darwin | arm64 | myapp |
| macOS Intel | darwin | amd64 | myapp |
Полный список поддерживаемых пар можно получить командой:
go tool dist list
На Go 1.21 список содержит более 50 комбинаций — от js/wasm до freebsd/riscv64.
Шаг 4. Автоматизируем сборку под две платформы
Вместо того чтобы каждый раз набирать команды руками, удобно завести Makefile (или скрипт) на два-три таргета:
# Makefile
APP_NAME := myapp
SRC := ./cmd/myapp
.PHONY: build-linux build-windows build-all
build-linux:
GOOS=linux GOARCH=amd64 go build -o dist/$(APP_NAME)-linux-amd64 $(SRC)
build-windows:
GOOS=windows GOARCH=amd64 go build -o dist/$(APP_NAME)-windows-amd64.exe $(SRC)
build-all: build-linux build-windows
@echo "Готово: бинарники в dist/"
Проверка: make build-all создаёт директорию dist/ с двумя файлами. Если Make у вас не установлен (типичная ситуация на Windows), подойдёт обычный bash-скрипт или PowerShell-скрипт с теми же командами.
Шаг 5. Добавляем версию в бинарник
Полезная практика — вшивать номер версии или хеш коммита прямо в исполняемый файл через -ldflags:
# Передаём значение переменной version в пакет main
GOOS=linux GOARCH=amd64 go build \
-ldflags "-X main.version=1.2.3" \
-o dist/myapp-linux-amd64 ./cmd/myapp
В коде достаточно объявить переменную:
package main
// Значение подставляется при сборке через -ldflags
var version = "dev"
При запуске myapp --version (если вы добавите обработку флага) выведется 1.2.3 вместо dev. Это бесплатный способ потом отличать бинарники друг от друга на сервере.
Ловушка: CGO и кросс-компиляция
Всё, что описано выше, работает для чистого Go-кода — когда проект не использует C-библиотеки. По умолчанию при кросс-компиляции Go выставляет CGO_ENABLED=0, и сборка проходит без проблем.
Проблема начинается, когда ваш код (или зависимость) тянет C через cgo. Типичные примеры:
mattn/go-sqlite3— обёртка над SQLite, требует C-компилятор;- некоторые пакеты для работы с сетевыми интерфейсами и DNS на Linux.
Если вы видите ошибку вида cgo: C compiler "gcc" not found или exec: "x86_64-w64-mingw32-gcc": executable file not found — это оно.
Что делать
- Проверить, нужен ли cgo. Многие пакеты имеют чисто Go-альтернативы. Например, вместо
mattn/go-sqlite3можно использоватьmodernc.org/sqlite— он не требует C-компилятора. - Явно отключить cgo, если зависимость это позволяет:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ... - Установить кросс-компилятор C, если cgo действительно нужен. На Ubuntu для сборки под Windows это пакет
gcc-mingw-w64-x86-64. Путь рабочий, но хрупкий — вы теряете главное преимущество Go: простоту кросс-компиляции.
Сноска. CGO — механизм вызова C-кода из Go. Когда CGO_ENABLED=1, компилятор Go генерирует обвязку для вызова функций, написанных на C, и для этого ему нужен C-компилятор, подходящий под целевую платформу. При CGO_ENABLED=0 все cgo-зависимости отключаются, и Go использует собственные реализации (например, чистый Go DNS-резолвер вместо системного).
Если не получилось
| Ошибка | Причина | Решение |
|---|---|---|
GOOS=linuxx: unsupported GOOS | Опечатка в имени ОС | Проверьте список: go tool dist list |
exec format error при запуске на целевой машине | Бинарник собран под другую архитектуру (например, arm64 вместо amd64) | Проверьте GOARCH и архитектуру целевой машины (uname -m) |
cgo: C compiler not found | CGO включён, а кросс-компилятора C нет | Добавьте CGO_ENABLED=0 перед командой или установите нужный gcc |
Бинарник собрался, но на целевой машине permission denied | На Linux-файле нет флага исполнения | chmod +x myapp после копирования |
| Бинарник работает, но не находит конфиги или .env-файлы | Пути зашиты относительно, а рабочая директория другая | Используйте абсолютные пути или передавайте через флаги / переменные окружения |
Что дальше
Вы получили два бинарника — для Windows и Linux. Следующие шаги:
- CI/CD. Перенесите Makefile-рецепты в GitHub Actions или GitLab CI. Образ
golang:1.21уже содержит всё необходимое для кросс-компиляции без CGO — ничего доустанавливать не нужно. - Сжатие бинарника. Утилита
upxуменьшает размер Go-бинарника в два-три раза. Но учитывайте, что некоторые антивирусы на Windows реагируют на UPX-сжатые файлы ложными срабатываниями. - Статическая линковка на Linux. С
CGO_ENABLED=0бинарник уже статический — он не зависит от glibc и запускается даже в минимальных Docker-образах вродеscratchилиdistroless. - Воспроизводимые сборки. Флаг
-trimpathубирает из бинарника абсолютные пути вашей машины, а-buildvcs=false— информацию о VCS. Пригодится, если вы распространяете бинарник публично.
Контент-конвейер: Из одной идеи — текст, изображение, озвучка и ролик. Один заказ вместо четырёх.
Спросить за 60 ₽Источники: Go Documentation — Installing Go from source (supported platforms), Go Blog — Go 1.5 Release Notes (cross-compilation changes)
Частые вопросы
Нужно ли устанавливать Go на целевую машину, чтобы запустить бинарник?
Можно ли собрать бинарник для macOS, находясь на Linux?
Что если в проекте используется go:embed — это ломает кросс-компиляцию?
Бинарник под Linux, собранный на Windows, не запускается — пишет exec format error. Что не так?
Как собрать сразу под пять платформ, не повторяя команды?
Кросс-компиляция замедляет сборку?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.