Docs menu
Go on Primcloud
How Primcloud detects a Go app, resolves the Go version, downloads modules, compiles binaries, caches between builds, and infers process types.
On this page
Primcloud detects a Go app from go.mod, resolves the Go SDK version from that file, downloads (or reads vendored) module dependencies, compiles every main package with go install, caches the SDK, module downloads, and build artifacts between builds, and, if you don't ship a Procfile, generates one process type per compiled binary. This page walks each phase and the config knobs that shape it.
Detection#
Primcloud runs the Go build when the root of your repo contains a go.mod file. That's the only trigger.
If go.mod isn't at the repo root, the build fails with a "no language detected" error before it starts. Only Go module projects are supported. The older dependency managers dep, godep, govendor, and glide aren't recognized.
Install phase#
Two things happen during install.
Go distribution. The build reads the go directive from go.mod and installs the matching Go toolchain (see Runtime versions). A // +heroku goVersion directive overrides the go directive when present.
Module downloads. If your repo has a vendor/modules.txt file, the build skips this step and uses the vendored copy of every dependency at compile time. Otherwise it runs go list ./... (which downloads the module graph as a side effect) and stores the result in a persistent module cache. Downloaded modules are verified against go.sum before use.
Compile phase#
The build runs a single command:
go install -tags heroku <package> [<package>...]
The package list is one of two things:
- Whatever you name in the
// +heroku installdirective ingo.mod, or - Every
mainpackage found by runninggo list -tags heroku -f '{{ if eq .Name "main" }}{{ .ImportPath }}{{ end }}' ./..., that is, every executable in the module.
Each compiled binary is written to $GOBIN inside the runtime image and is available on PATH by its package-name suffix.
Build tags. The -tags heroku flag is always passed to both go list and go install. Files gated with //go:build heroku are included in the build; files gated with //go:build !heroku are excluded.
Environment. During compile, these variables are set:
| Variable | Value | Purpose |
|---|---|---|
GOROOT |
Path to the installed Go distribution | Where the toolchain lives. |
GOBIN |
Path to the target layer's bin/ |
Where compiled binaries land. |
GOCACHE |
Path to the build cache | Incremental compile cache. |
GOMODCACHE |
Path to the module cache | Downloaded modules. |
GO111MODULE |
on |
Forces module-mode compilation. |
Any GO-prefixed variables you set in the Secrets panel are visible during the build. The build prints a warning listing them when it starts. It won't stop you from setting them, but they can change build behavior in ways you might not expect.
Cache behavior#
Primcloud persists a per-app build cache between deploys. Three things are reused across builds when their inputs haven't changed:
- The Go distribution: reused as long as the resolved SDK (version, OS, and architecture) matches the previous build.
- Downloaded modules: the contents of
$GOMODCACHE, reused across builds and refreshed periodically so the cache stays bounded. - Compile artifacts: the incremental build cache under
$GOCACHE, reused as long as the Go major version, target architecture, and base image haven't changed.
Vendored apps (those with vendor/modules.txt) skip the module download step entirely and don't populate the module cache; the compile-artifact cache still applies.
Release phase conventions#
Procfile takes precedence. If your repo has a Procfile at the root, Primcloud uses it verbatim to define process types, the same as every other language on Primcloud. When a Procfile is present, the build leaves process definitions entirely to your file. See Pod Formations for the format.
Auto-detected process types kick in when there's no Procfile. One process type is generated per compiled binary:
- Each process is named after the trailing path segment of the package's import path, lowercased. For
example.com/myapp/cmd/worker, that'sworker. - The command for each process is the binary name: the process type doubles as the executable, since
$GOBINis onPATH. - One process is the default
web. If any compiled package is namedweb, that one wins. Otherwise the first package is duplicated as a second entry namedweb, so the formation always has awebprocess to start.
Only web and worker are real pod types. web is the internet-facing process (one replica by default, never auto-removed); every other process name becomes a worker formation, created at zero replicas until you scale it up. See Pod Formations.
Port binding. Primcloud sets PORT on the web process (the constant 5000), and your web server must bind to it. The Go build does not rewrite your launch command to inject the port, so your code has to read it:
port := os.Getenv("PORT")
if port == "" {
log.Fatal("$PORT must be set")
}
router.Run(":" + port)
Non-web formations get no PORT; a worker that doesn't serve HTTP doesn't need one. Any port other than 5000 is unreachable from outside the pod.
Runtime environment. No Go-specific environment variables are set at runtime. Only the platform-provided ones (PORT, plus anything you configure in the Secrets panel) reach the running process. The Go toolchain and module cache aren't shipped in the runtime image; only the compiled binaries under $GOBIN are.