# Go apps

> Install the Go toolchain, deploy a Go service from Git with automatic build and start commands, listen on PORT, set environment variables and fix common errors.

Source: https://zopanel.net/docs/go  
Updated: 2026-10-09

A Go application runs on ZoPanel as a server app: ZoPanel compiles it from source with the official Go toolchain, starts the binary as a service of the hosting account behind nginx, and releases new versions with no downtime. Use this page for HTTP services and APIs written in Go; the general mechanics (ports, blue/green releases, logs) are described in [Node.js and Python apps](/docs/apps-node-python).

## Install Go on the server

An administrator installs Go once:

1. Open **Runtimes**.
2. In the **Go** card ("Official Go toolchain."), click **Install latest**.

ZoPanel downloads the newest stable release for the server's architecture (amd64 or arm64) from `go.dev`, verifies its SHA-256 checksum and installs it in `/opt/zopanel/runtimes/go/<major.minor>`, for example `1.25`. Installing again later adds the newer minor version next to the old one. Remove a version with the trash icon on its badge.

**Version selection:** a deploy uses the installed version that matches the `go` line of `go.mod` (`go 1.24` → `1.24`), otherwise the newest installed version. When `go.mod` asks for a newer Go than the one used, Go 1.21 and later download the required toolchain automatically during the build ([Go toolchains](https://go.dev/doc/toolchain)); installing the latest version avoids that download.

## Prepare your app

Your program must listen on `127.0.0.1` at the port in the `PORT` environment variable. ZoPanel sets `PORT` (and `HOST=127.0.0.1`) for every release; the second blue/green slot uses another port, so never hard-code it.

```go
package main

import (
	"net/http"
	"os"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello from Go"))
	})
	http.ListenAndServe("127.0.0.1:"+os.Getenv("PORT"), mux)
}
```

nginx terminates SSL and forwards every request to the app with `Host`, `X-Real-IP`, `X-Forwarded-For` and `X-Forwarded-Proto` headers. Read the client address from `X-Forwarded-For` or `X-Real-IP`, not from `r.RemoteAddr`, which is always `127.0.0.1`.

## Deploy

1. In **Websites → New website**, choose **Git deploy**, enter the **Repository URL** and **Branch** (and **Root directory** if `go.mod` is in a subfolder), then click **Create website**. For an existing website, use its **Deploy** tab.
2. ZoPanel detects Go from `go.mod` and builds the first release.
3. Add your settings in **Environment variables** and click **Save & redeploy**.

You can also upload the source to `domains/<domain>/source` and choose **Uploaded files (File Manager)** as the **Source** (see [Git deploy](/docs/git-deploy)).

### Detected build and start commands

| Repository layout | Build command | Start command |
| --- | --- | --- |
| `main.go` at the root | `CGO_ENABLED=0 go build -o bin/<name> .` | `./bin/<name>` |
| `cmd/<cmd>/main.go` | `mkdir -p bin && CGO_ENABLED=0 go build -o bin/ ./cmd/...` | `./bin/<first cmd in alphabetical order>` |
| Neither | `CGO_ENABLED=0 go build -o bin/<name> .` | `./bin/<name>` |

- `<name>` is the last element of the `module` path in `go.mod` (`github.com/acme/api` → `api`), or `app`.
- The install command is `go mod download`.
- With several commands in `cmd/`, the deploy log notes "Multiple commands found in cmd/, starting …". To start another one, turn **Auto-detect** off and edit **Start command**, for example `./bin/server`.
- `bin/` is added to `PATH` when the app starts, so a `Procfile` line such as `web: api` works. A `web:` line in a `Procfile` always provides the start command.

To change any command, turn **Auto-detect** off in the **Build & run** card, edit it and click **Save & deploy**. Build steps chained with `&&` stay on one line, for example `go generate ./... && CGO_ENABLED=0 go build -o bin/api .`.

### Build environment

- Builds run as the hosting account, inside its CPU and memory limits, with at most 30 minutes for the install command and 30 minutes for the build.
- The module cache (`GOPATH`) and build cache (`GOCACHE`) live in the account's home and are kept between deploys, so later builds are faster.
- `CGO_ENABLED=0` produces a static binary. Packages that need cgo (for example `github.com/mattn/go-sqlite3`) need `CGO_ENABLED=1` and a C compiler on the server; prefer pure-Go alternatives such as `modernc.org/sqlite`.

## Environment variables

Put configuration and secrets in **Environment variables** on the **Deploy** tab. They are available during the build and at runtime; **Paste .env** adds many at once (up to 200 variables, single-line values). Members with view-only access cannot see the values.

Example for a MariaDB database created under **Databases**:

```dotenv
DATABASE_URL=alice_api:your-password@tcp(127.0.0.1:3306)/alice_api?parseTime=true
```

The format of the connection string depends on the driver you use; the host is `127.0.0.1` (see [Databases](/docs/databases)).

Files that must survive a release, such as an upload folder or a SQLite database, go in **Persistent paths** (for example `data`). Everything else in the release folder is replaced on every deploy.

## Releases, logs and restarts

- A new release starts in the idle slot and must answer the **Health check path** (default `/`) with a status below 500 within 90 seconds. Only then does nginx switch to it and the old process stop. If the binary exits or never answers, the running release is kept.
- **Releases** keeps the last 5 builds for **Rollback**.
- **Application output** on the **Deploy** tab shows the last 400 lines your program writes to stdout and stderr; turn on **Live** to follow it. Write logs to stdout instead of files.
- The service restarts automatically if the process exits. Stop the program cleanly on `SIGTERM`: during a release the old process gets 20 seconds before it is killed.

Memory, CPU and processes count toward the account's package (see [Packages and limits](/docs/packages-limits)).

## Troubleshooting

| Message or symptom | What to do |
| --- | --- |
| "Go is not installed — install it under Runtimes" | Ask the administrator to click **Install latest** in the **Go** card. |
| "the app did not respond on port … within 90s (it must listen on $PORT)" | Listen on `127.0.0.1:$PORT`. A fixed port such as `:8080` never receives traffic. |
| "the app exited during startup" | Read **Application output**: usually a missing environment variable or a failed database connection. |
| `install failed` during `go mod download` | A private module cannot be fetched. Vendor your dependencies (`go mod vendor`), commit `vendor/`, then turn **Auto-detect** off and clear the **Install command**: `go build` uses `vendor/` automatically. |
| `go: go.mod requires go >= …` | Install the latest Go under **Runtimes**, or let the toolchain download complete. |
| Build fails with cgo errors | Use a pure-Go package, or set `CGO_ENABLED=1` in the build command if a C compiler is installed. |
| The repository is detected as Node.js | A `package.json` at the root takes priority over `go.mod`. Turn **Auto-detect** off, set **Runtime** to `go`, **Type** to **Server app (process)** and enter the commands yourself. |
| 502 Bad Gateway | The process is not listening. Check **Application output** and **Run diagnostics** on the **Logs** tab. |

## Related

- [Node.js and Python apps](/docs/apps-node-python)
- [Git deploy](/docs/git-deploy)
- [Databases](/docs/databases)
- [Packages and limits](/docs/packages-limits)
- [Go documentation](https://go.dev/doc/)
