Skip to content

Deploying Go on Hop3

A Go app on Hop3 is the simplest kind of long-running service: go build produces a single self-contained binary, Hop3 runs that binary, and the binary listens on a TCP port that nginx proxies to. There is no interpreter to install at runtime and no application server (no uWSGI, no Gunicorn) — the binary is the server. The framework you pick (Gin, Fiber, or the stdlib net/http) only changes how you write handlers; the deployment shape is identical.

This page covers what every Go framework on Hop3 shares. Read it first, then follow the per-framework guide below.

How Hop3 builds and runs Go

Hop3 detects a Go app from its go.mod and uses the Go toolchain to build it. The build happens on the server (or in the build container) on every deploy:

  1. Fetch & build — Hop3 runs your before-build command, which is just go build. The recommended form strips debug info for a smaller binary: go build -ldflags='-s -w' -o <app> .. Go's module cache means dependencies are only re-downloaded when go.mod/go.sum change, so repeat builds are fast.
  2. Run — Hop3 starts the binary named in [run].start (e.g. ./hop3-tuto-gin) and supervises it as a worker.
  3. Proxy — Hop3 assigns a dynamic port via the $PORT environment variable and points nginx at it. Your binary must read $PORT and bind to it — a hardcoded port will not receive traffic. The universal idiom is:
port := os.Getenv("PORT")
if port == "" {
    port = "8080" // local dev fallback
}
r.Run(":" + port) // or app.Listen(":" + port), or http.ListenAndServe(":"+port, ...)

A representative hop3.toml shared by every Go framework here:

[metadata]
id = "my-go-app"
version = "1.0.0"

[build]
before-build = ["go build -ldflags='-s -w' -o my-go-app ."]
packages = ["golang"]

[run]
start = "./my-go-app"

[port]
web = 8080

[healthcheck]
path = "/up"
timeout = 30
interval = 60

[port].web is the local fallback the tutorials bind to; in production Hop3 overrides it with $PORT. The [healthcheck] path should be a cheap endpoint that returns 200 (the tutorials use a tiny /up handler) so Hop3 can confirm the binary came up before sending traffic.

Cross-cutting notes

  • Config via environment. Hop3 injects configuration as env vars; read everything through os.Getenv. Set values with hop3 config set --app <app> KEY=value. The most important is HOST_NAME, which tells nginx which domain to serve — set it, then redeploy so nginx picks it up.
  • Addons are env vars too. Attaching a database or cache exposes a connection string: PostgreSQL via DATABASE_URL, Redis via REDIS_URL. Pass them straight to your driver, e.g. gorm.Open(postgres.Open(os.Getenv("DATABASE_URL")), ...) or redis.ParseURL(os.Getenv("REDIS_URL")). No host/port wiring on your side.
  • Release mode. Frameworks behave differently in dev vs. production. For Gin, set GIN_MODE=release (the tutorial puts it in [env]) to disable debug logging and the dev banner.
  • Don't commit the binary. The binary is a build artifact rebuilt on every deploy — add it (and vendor/) to .gitignore.
  • The deploy flow is the same for all of them. hop3 deploy <app> (first deploy creates the app) → hop3 config set --app <app> HOST_NAME=<app>.<your-domain> → deploy again to apply → verify with hop3 app status and a curl of the healthcheck path. After that, manage with hop3 app logs, hop3 app restart, hop3 config show, and hop3 ps scale --app <app> web=N.

Choose a framework

Framework Tutorial Good for
Gin A full Gin REST API with health, info, and CRUD endpoints The most popular Go web framework — batteries-included routing, JSON binding, middleware
Fiber A Fiber app built on Fasthttp with logging, CORS, and recovery middleware An Express-style API and maximum raw throughput

Both produce a single binary that binds $PORT, so the hop3.toml and deploy steps are nearly identical — pick the framework whose API you prefer. Want a static site generated by Go (Hugo) instead of a service? That lives under Static Sites.