Deploy a Go app

Deploy a Go web server on Hangar from its repository, built as a static binary with no Dockerfile, with PostgreSQL and HTTPS on your domain.

Updated

A Go server deploys on Hangar straight from its repository. The automatic builder compiles it into a small static binary and runs it as a long-lived process, so goroutines, WebSockets and in-memory caches work as they do on your machine.

Before you start

  • A Go module in a Git repository, with go.mod at the root.
  • A Hangar account.

1. Listen on the right address

Read the port from PORT and listen on all interfaces. A Go address such as :8080 already means every interface:

port := os.Getenv("PORT")
if port == "" {
	port = "8080"
}
log.Fatal(http.ListenAndServe(":"+port, mux))

Do not listen on 127.0.0.1:8080: the proxy in front of your service could not reach it.

2. Create the service

Create a project, add a service and pick your repository and branch. Choose the region closest to your users and leave the builder on Automatic. It detects go.mod, uses the Go version declared there, downloads the modules and builds the binary.

If the repository has several programs, the builder takes the root package when it has Go files, otherwise the first directory under cmd/. To pick another one, set this variable in the Variables tab:

RAILPACK_GO_BIN=cmd/api

3. Set the variables

Hangar sets PORT to the domain's container port, so the code above listens on 8080 once you add the domain below. Set your own configuration in the Variables tab.

4. Deploy and add a domain

Deploy the service. In Settings → Networking, add a domain with container port 8080 and HTTPS on.

Adding PostgreSQL

Add a PostgreSQL service in the same project and region, copy its internal connection URL and set it on the app:

DATABASE_URL=<the internal connection URL>

With pgx:

pool, err := pgxpool.New(ctx, os.Getenv("DATABASE_URL"))

Run migrations, with a tool such as goose or golang-migrate, from your program at startup or as a cron job you run by hand.

Shut down cleanly

On every deploy the old container receives SIGTERM before it stops. Catch it and let requests in flight finish:

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
go srv.ListenAndServe()
<-ctx.Done()
srv.Shutdown(context.Background())

Troubleshooting

  • 502 on the domain: the server listens on 127.0.0.1, or on a port other than the domain's container port.
  • The wrong program starts: set RAILPACK_GO_BIN to the package you want.
  • cgo or C library errors: set CGO_ENABLED=1, or build with a Dockerfile.

Frequently asked questions

Do I need a Dockerfile to deploy Go on Hangar?
No. The automatic builder detects go.mod, compiles your app into a static binary with the Go version from go.mod and runs it. A Dockerfile is only needed for unusual builds.
How does Hangar pick the package to build in a repository with several commands?
It builds the root package if it has Go files, otherwise the first directory under cmd/. To choose another one, set the RAILPACK_GO_BIN variable to its path, for example cmd/api.
Does cgo work?
Builds use CGO_ENABLED=0 by default, which gives a static binary. Set the CGO_ENABLED=1 variable if your app needs cgo, for example for SQLite through mattn/go-sqlite3.

Deploy your app on Hangar

Connect a repository and it is live in minutes.

Start deploying