You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As part of #8307 (PR #8599) azd gained support for deploying Go Azure Functions. Today this requires the user to have the Go toolchain pre-installed and on PATH (cli/azd/pkg/tools/golang/golang.go runs go version, go mod download, go build, min version 1.24).
This issue proposes investigating/implementing azd downloading the Go toolchain itself — the same self-install pattern azd already uses for the Bicep CLI (cli/azd/pkg/tools/bicep/bicep.go) and the GitHub CLI (cli/azd/pkg/tools/github/github.go), which download per-OS/arch binaries into config.GetUserConfigDir()/bin/ and execute them from there.
Feasibility (verified)
This was validated empirically: the official Go archive (https://go.dev/dl/go<ver>.<os>-<arch>.tar.gz, .zip on Windows) is fully self-contained and relocatable. After extracting to a non-standard path, with a clean environment (empty PATH, no GOROOT, no system Go):
go/bin/go version works — GOROOT is auto-resolved from the binary's own location.
Cross-compiling to linux/amd64 (the Functions target) and to other targets (e.g. windows/arm64) succeeds with CGO_ENABLED=0 — no gcc/clang or system toolchain required.
The azd Functions build path already sets GOOS=linux GOARCH=amd64 CGO_ENABLED=0 (cli/azd/pkg/project/framework_service_go.go), which is exactly the dependency-free mode. So azd can download the host Go archive once and cross-compile to the target, just like the current code does.
Trade-offs
Aspect
Win (bundle Go in azd)
Cost / Risk
Onboarding
User runs azd up on a Go Functions app with zero prep — no "install Go first" step
—
Consistency
azd controls the exact Go version used to build; reproducible across machines/CI
Must track & bump the pinned Go version (another tool to maintain, like Bicep/gh)
Disk footprint
—
~71 MB download, ~269 MB extracted per version (vs ~10 MB Bicep). Largest self-installed tool by far
First-build latency
—
One-time download + extract before first build; needs progress UX
Offline / determinism
Can pin GOTOOLCHAIN to avoid surprise toolchain downloads
If a user's go.mod requires a newer go, Go 1.21+ auto-downloads a matching toolchain unless pinned
Caches
azd can isolate GOCACHE/GOMODCACHE under ~/.azd
Extra disk under azd's dir; go mod download still needs network
Maintenance
Reuses the proven Bicep/gh self-install pattern
New code path, checksum verification, per-OS/arch URL matrix to keep working
Respecting user setup
Keep an AZD_GO_TOOL_PATH override + prefer on-PATHgo if present
Slight added complexity in resolution logic
⚠️ Is this actually worth it?
Go is generally easy to install (single archive, winget/brew/apt, no complex system changes). If the team judges that "please install Go" is an acceptable one-time ask, the ~269 MB on-disk cost + maintenance of yet another pinned tool may outweigh the convenience. Unlike Bicep (niche, hard to discover) or a C/cross toolchain (genuinely painful), Go's installer story is good. This proposal should only proceed if the friction-removal is deemed clearly worth that cost — otherwise simply requiring a Go install (current behavior) is a reasonable default.
Broader benefit: azd extensions
A bundled, self-managed Go toolchain isn't only useful for Go Functions. It would let azd build Go code without the user having Go installed, which unlocks extension scenarios such as:
Installing an extension from source (azd ext building a Go extension locally).
Installing/trying an extension from a specific PR or branch by compiling it on the fly.
This makes a shared internal "Go-as-a-tool" capability (download + build) potentially reusable beyond the Functions framework service.
Implementation Plan
The goal: make Go a self-installed tool following the existing Bicep pattern, while preserving the ability to use a user-provided Go.
1. Add download/install to the Go tool wrapper
File: cli/azd/pkg/tools/golang/golang.go (model on cli/azd/pkg/tools/bicep/bicep.go).
Add a pinned Version constant (e.g. 1.24.x, ≥ the current MinimumVersion).
azdGoPath() → resolve install dir under config.GetUserConfigDir() (e.g. <configDir>/go/<version>/), exec path <dir>/go/bin/go (.exe on Windows).
downloadGo(ctx):
Build per-OS/arch release name: go<ver>.<goos>-<goarch>.<ext> where ext = zip (Windows) / tar.gz (Linux, macOS), arch in Go's naming (amd64, arm64).
URL base: https://go.dev/dl/.
Verify checksum — Go publishes SHA256 per file; the version index is available at https://go.dev/dl/?mode=json (and ...?mode=json&include=all). Match Bicep's integrity approach.
Extract .zip/.tar.gz into the install dir (reuse extraction helpers like those in github.go's extractFromZip/extractFromTar; preserve executable bits on go/bin/go).
ensureInstalled(ctx) (lazy, sync.Once-style like Bicep):
Honor AZD_GO_TOOL_PATH env override.
If a suitable go is already on PATH and meets MinimumVersion, optionally prefer it (decide policy; see step 5).
Else if azdGoPath() exists and version OK, use it.
Else download.
Update CheckInstalled to call ensureInstalled instead of erroring when go is missing.
2. Execute the bundled Go correctly
In Build/ModDownload (golang.go):
Run the resolved go path (not the literal "go").
Set GOROOT=<installDir>/go explicitly (defensive; auto-detect also works).
Set GOCACHE / GOMODCACHE to azd-managed dirs under config.GetUserConfigDir() for isolation (optional but recommended).
Consider setting GOTOOLCHAIN policy (e.g. local for determinism, or leave auto to allow go.mod-driven upgrades). Document the choice.
3. Wire-up / DI
Confirm cli/azd/cmd/container.go registration of golang.NewCli still holds (no constructor signature change needed if download is internal/lazy).
framework_service_go.goRequiredExternalTools continues to return the Go CLI; the difference is CheckInstalled now self-heals by downloading.
4. Tests
Unit-test URL/release-name construction across all OS/arch combos (mirror Bicep tests).
Unit-test version parsing/comparison and the AZD_GO_TOOL_PATH / prefer-PATH logic.
Extraction test (zip + tar.gz) preserving the executable bit.
Follow repo testing rules in cli/azd/AGENTS.md (no writes to os.Stdout, table-driven tests, etc.).
5. Resolution policy & UX (decide explicitly)
Prefer user's Go vs always bundle? Recommended: prefer a valid on-PATH Go (fast, no download); fall back to bundling. Add AZD_GO_TOOL_PATH override either way.
Progress UX: show a download/extract progress indicator (Go is ~71 MB). Follow cli/azd/docs/style-guidelines/azd-style-guide.md.
6. Documentation
cli/azd/docs/environment-variables.md: document AZD_GO_TOOL_PATH (+ any GOTOOLCHAIN/cache behavior azd sets), verifying parsing semantics against source.
Note the new self-install behavior wherever Bicep/gh auto-install is described.
7. (Optional, follow-up) Generalize for extensions
Extract a reusable "ensure Go toolchain" capability so azd ext can build Go extensions from source / from a PR without a user Go install. Track as a separate issue once the Functions path lands.
Acceptance criteria
On a machine without Go installed, azd up on the gofuncapp sample builds and deploys successfully.
A user-provided Go on PATH (and AZD_GO_TOOL_PATH) is still honored.
Download is checksum-verified, cached per version, and re-used across runs.
Pre-commit checks pass (gofmt, golangci-lint, cspell, copyright, build, tests) — e.g. via the /azd-preflight skill.
Summary
As part of #8307 (PR #8599) azd gained support for deploying Go Azure Functions. Today this requires the user to have the Go toolchain pre-installed and on
PATH(cli/azd/pkg/tools/golang/golang.gorunsgo version,go mod download,go build, min version 1.24).This issue proposes investigating/implementing azd downloading the Go toolchain itself — the same self-install pattern azd already uses for the Bicep CLI (
cli/azd/pkg/tools/bicep/bicep.go) and the GitHub CLI (cli/azd/pkg/tools/github/github.go), which download per-OS/arch binaries intoconfig.GetUserConfigDir()/bin/and execute them from there.Feasibility (verified)
This was validated empirically: the official Go archive (
https://go.dev/dl/go<ver>.<os>-<arch>.tar.gz,.zipon Windows) is fully self-contained and relocatable. After extracting to a non-standard path, with a clean environment (emptyPATH, noGOROOT, no system Go):go/bin/go versionworks —GOROOTis auto-resolved from the binary's own location.CGO_ENABLED=0— no gcc/clang or system toolchain required.The azd Functions build path already sets
GOOS=linux GOARCH=amd64 CGO_ENABLED=0(cli/azd/pkg/project/framework_service_go.go), which is exactly the dependency-free mode. So azd can download the host Go archive once and cross-compile to the target, just like the current code does.Trade-offs
azd upon a Go Functions app with zero prep — no "install Go first" stepGOTOOLCHAINto avoid surprise toolchain downloadsgo.modrequires a newergo, Go 1.21+ auto-downloads a matching toolchain unless pinnedGOCACHE/GOMODCACHEunder~/.azdgo mod downloadstill needs networkAZD_GO_TOOL_PATHoverride + prefer on-PATHgoif presentGo is generally easy to install (single archive,
winget/brew/apt, no complex system changes). If the team judges that "please install Go" is an acceptable one-time ask, the ~269 MB on-disk cost + maintenance of yet another pinned tool may outweigh the convenience. Unlike Bicep (niche, hard to discover) or a C/cross toolchain (genuinely painful), Go's installer story is good. This proposal should only proceed if the friction-removal is deemed clearly worth that cost — otherwise simply requiring a Go install (current behavior) is a reasonable default.Broader benefit:
azd extensionsA bundled, self-managed Go toolchain isn't only useful for Go Functions. It would let azd build Go code without the user having Go installed, which unlocks extension scenarios such as:
azd extbuilding a Go extension locally).This makes a shared internal "Go-as-a-tool" capability (download + build) potentially reusable beyond the Functions framework service.
Implementation Plan
The goal: make Go a self-installed tool following the existing Bicep pattern, while preserving the ability to use a user-provided Go.
1. Add download/install to the Go tool wrapper
File:
cli/azd/pkg/tools/golang/golang.go(model oncli/azd/pkg/tools/bicep/bicep.go).Versionconstant (e.g.1.24.x, ≥ the currentMinimumVersion).azdGoPath()→ resolve install dir underconfig.GetUserConfigDir()(e.g.<configDir>/go/<version>/), exec path<dir>/go/bin/go(.exeon Windows).downloadGo(ctx):go<ver>.<goos>-<goarch>.<ext>whereext=zip(Windows) /tar.gz(Linux, macOS), arch in Go's naming (amd64,arm64).https://go.dev/dl/.https://go.dev/dl/?mode=json(and...?mode=json&include=all). Match Bicep's integrity approach..zip/.tar.gzinto the install dir (reuse extraction helpers like those ingithub.go'sextractFromZip/extractFromTar; preserve executable bits ongo/bin/go).ensureInstalled(ctx)(lazy,sync.Once-style like Bicep):AZD_GO_TOOL_PATHenv override.gois already onPATHand meetsMinimumVersion, optionally prefer it (decide policy; see step 5).azdGoPath()exists and version OK, use it.CheckInstalledto callensureInstalledinstead of erroring whengois missing.2. Execute the bundled Go correctly
In
Build/ModDownload(golang.go):gopath (not the literal"go").GOROOT=<installDir>/goexplicitly (defensive; auto-detect also works).GOCACHE/GOMODCACHEto azd-managed dirs underconfig.GetUserConfigDir()for isolation (optional but recommended).GOTOOLCHAINpolicy (e.g.localfor determinism, or leaveautoto allowgo.mod-driven upgrades). Document the choice.3. Wire-up / DI
cli/azd/cmd/container.goregistration ofgolang.NewClistill holds (no constructor signature change needed if download is internal/lazy).framework_service_go.goRequiredExternalToolscontinues to return the Go CLI; the difference isCheckInstallednow self-heals by downloading.4. Tests
AZD_GO_TOOL_PATH/ prefer-PATH logic.Test_CLI_Up_Down_GoFuncApp(added in Add Go Azure Functions framework service support #8599) green; ensure it works without a system Go when bundling is enabled.cli/azd/AGENTS.md(no writes toos.Stdout, table-driven tests, etc.).5. Resolution policy & UX (decide explicitly)
PATHGo (fast, no download); fall back to bundling. AddAZD_GO_TOOL_PATHoverride either way.cli/azd/docs/style-guidelines/azd-style-guide.md.6. Documentation
cli/azd/docs/environment-variables.md: documentAZD_GO_TOOL_PATH(+ anyGOTOOLCHAIN/cache behavior azd sets), verifying parsing semantics against source.7. (Optional, follow-up) Generalize for extensions
azd extcan build Go extensions from source / from a PR without a user Go install. Track as a separate issue once the Functions path lands.Acceptance criteria
azd upon thegofuncappsample builds and deploys successfully.PATH(andAZD_GO_TOOL_PATH) is still honored.gofmt,golangci-lint,cspell, copyright, build, tests) — e.g. via the/azd-preflightskill.References
cli/azd/pkg/tools/bicep/bicep.go,cli/azd/pkg/tools/github/github.gocli/azd/pkg/tools/golang/golang.go,cli/azd/pkg/project/framework_service_go.go