Deploy Go apps in Europe
A Go service on EU infrastructure in a distroless image: built on the newest Go, shipped as a static binary, running in Helsinki with managed Postgres next to it.
Go is the easiest stack to run small. HostStack builds your module on the newest Go release with CGO turned off and copies the result onto Google’s distroless static image, so the running container is little more than your binary. A Go API idles comfortably on the smallest paid size, and the whole thing runs in Helsinki under a Danish company.
What HostStack detects and builds
- Go is recognised from go.mod — checked before every other runtime, so a repo with go.mod and package.json builds as Go.
- The build runs on the newest Go image with GOTOOLCHAIN=auto, so a module whose go.mod asks for a newer toolchain fetches it rather than failing.
- CGO_ENABLED=0: the binary is static, because the runtime image has no C library.
- The runtime image is gcr.io/distroless/static-debian13 — no shell, no package manager — with your built tree copied in.
- go mod download runs in its own layer, so a deploy that does not touch go.mod or go.sum reuses the module cache.
| Step | Default when you leave it empty |
|---|---|
| Install | go mod download |
| Build | go build -o /app/server . |
| Start | ./server |
| Port | PORT=3000 — read it with os.Getenv("PORT") |
The default build compiles the package at the repo root. If main lives in cmd/<name>, change the build command to point at it.
hoststack.yaml for Go
Optional. Commit it next to your code and every deploy applies it; leave a key out and the service keeps its dashboard value. It configures a service that already exists — it never creates one. The full schema and hoststack validate are in the docs.
services:
api:
build:
command: go build -o /app/server ./cmd/api
start:
command: ./server
healthCheck:
path: /healthzKeep the start command a plain binary path. Anything with $, &&, | or ; in it is run through /bin/sh, and the distroless image has none — read PORT in your code instead.
Database and environment variables
Link a managed Postgres and DATABASE_URL is injected; pgx and database/sql with lib/pq both take the postgresql:// URL as it is. A linked Redis arrives as REDIS_URL.
hoststack db create --project prj_... --name app-db --engine postgres --plan micro
hoststack db link db_... --service svc_...
hoststack env set svc_... API_TOKEN=... --secret
hoststack services update svc_... --pre-deploy-command "./server migrate"Secrets belong on the service, not in the file — see environment variables and databases.
Deploy Go step by step
- 1Read the port with os.Getenv("PORT") and listen on ":" + port.
- 2Create a web service from your repository, in the dashboard or with hoststack services create --name api --type web_service --project prj_... --repo acme/api.
- 3Commit the hoststack.yaml above, pointing the build at your main package.
- 4Create a managed Postgres and link it; DATABASE_URL is injected on the next deploy.
- 5Give the binary a migrate subcommand (golang-migrate and goose both embed) and set ./server migrate as the pre-deploy command, so the schema is current before traffic switches.
- 6Push to deploy. Each push builds a fresh binary and switches traffic once it is healthy.
Hosted in Europe, by a European company
Go services often sit on the hot path — the API, the ingest endpoint, the webhook receiver. Keeping them in an EU region, run by an EU company, means the requests and the logs they leave behind never leave the EU.
Every service, database and backup runs in Helsinki, Finland, on Hetzner, and HostStack is operated by MICCI, a Danish company — so the US CLOUD Act has no hold on it, the GDPR data processing agreement is signed at signup on every plan, and the invoice is in EUR. Why that differs from a US platform's EU region is on EU data residency.
Things that catch people out
CGO is off.
Packages that need a C compiler at build time, such as mattn/go-sqlite3, do not build. Use a pure-Go alternative like modernc.org/sqlite, or bring a Dockerfile.
There is no shell in the runtime image.
A start command such as ./server --port $PORT is run through /bin/sh, which distroless does not have, and the container exits at once — so read PORT with os.Getenv and keep the start command ./server. The pre-deploy command runs directly instead of through a shell here, so it must be a program and its arguments: ./server migrate works, ./server migrate && ./server seed fails the deploy with a message saying why.
The newest Go, not the one in go.mod.
The go line in go.mod is a minimum, so the build always uses the latest Go release. If you must pin an exact version, build from your own Dockerfile.
Frequently asked questions
How do I deploy a Go app in Europe?
Connect the repo to HostStack. It detects Go from go.mod, builds a static binary on the newest Go, and runs it on a distroless image in Helsinki, Finland. Your server reads PORT from the environment; link a managed Postgres for DATABASE_URL.
Which Go version does HostStack build with?
The newest Go release, with GOTOOLCHAIN=auto so a go.mod that asks for a newer toolchain can fetch it. The go and toolchain lines in go.mod are treated as minimums, which is what they mean to the Go tool itself.
Can my Go binary serve templates and static files?
Yes. The whole built tree is copied into the runtime image, so files in your repo are there at run time next to the binary; embedding them with go:embed works too.
What is the cheapest way to run a Go service?
Pico, at €1/month for 128 MB and a tenth of a vCPU, is always on and enough for many small Go APIs. The free tier’s Nano (256 MB) costs nothing but sleeps after 60 minutes without traffic.
Other stacks
All guides are on the deploy guides index. Moving an existing Go app? The Fly.io, Render and Railway guides have the cutover order.