Go Preview Environment in 5 Minutes — Live URLs for Every Branch, No Kubernetes
Your Go service compiles in 3 seconds. Your CI pipeline runs tests in 12. And then your designer asks for a link to the branch you pushed 40 minutes ago, and you're still writing a Docker Compose override so two PRs don't fight over port 8080.
Go's build speed is a superpower. The preview workflow around it usually isn't.
Most Go teams handle branch previews the same way: a shared staging server that one person deploys to at a time, or a hand-rolled CI script that provisions a cloud VM, wires up DNS, and hopes the cleanup cron didn't silently fail three weeks ago. Neither approach keeps up with how fast Go teams actually ship.
PreviewDrop gives every Go branch its own live URL — automatic deploys, HTTPS by default, database isolation, and zero CI configuration. Here's how to set it up in five minutes.
Why Go previews are different from frontend previews
Vercel and Netlify built the "push a branch, get a URL" workflow for JavaScript frontends. That workflow assumes your app compiles to static files or serverless functions. Go compiles to a single binary that listens on a port — a long-lived process that needs a container, not a static host.
The tools that do handle container-based previews — Railway, Render, Northflank — charge per resource or per second. A Go service with a Postgres dependency can cost $40–80/month in preview infrastructure alone for a team of three engineers. The meter runs whether anyone is reviewing the branch or not.
PreviewDrop takes a different approach: it detects Go from your go.mod and builds the image for you — no Dockerfile needed — so you get a live URL on every branch. Flat pricing, no per-resource metering, automatic teardown when the PR merges.
Step-by-step: your first Go preview
Step 1 — Make sure your Go service reads PORT. PreviewDrop builds from your go.mod and go.sum — you don't need a Dockerfile, and one in the repo isn't used. Your server must listen on the injected PORT (see the gotchas below). If your entry point isn't where detection expects it, set the Build and Start commands in Project Settings:
# Build command
CGO_ENABLED=0 go build -o server ./cmd/api
# Start command
./server
Step 2 — Connect your repo. Install the PreviewDrop GitHub App on your Go repo. It takes about 60 seconds. The app receives push and PR webhooks automatically — no workflow files to write.
Step 3 — Set environment variables. In the PreviewDrop dashboard, add the variables your Go service needs:
DATABASE_URL=postgres://user:pass@host:5432/preview_db
API_SECRET=your-preview-secret
Use a dedicated development database. PreviewDrop previews are ephemeral — point DATABASE_URL at a shared dev Postgres instance, or use a database-branching service for per-branch isolation.
Step 4 — Push a branch. That's it. PreviewDrop detects Go from your go.mod, builds the image, starts the container, and posts a live HTTPS URL to your PR — typically within a minute or two.
git checkout -b feature/new-api-endpoint
git push origin feature/new-api-endpoint
# URL posted to your PR automatically:
# https://pr-127.previewdrop.dev
What PreviewDrop detects in your Go project
When PreviewDrop scans your repo, it looks for:
go.mod— confirms it's a Go project, reads the module path and Go versionmain.goorcmd/*/main.go— identifies the entry pointMakefileor build scripts — detects custom build commands if you've configured them
The build process compiles the binary and starts the container. If your project uses go generate or code generation, override the Build command in Project Settings (for example go generate ./... && go build -o server ./cmd/api).
Database strategy for Go previews
Go services typically connect to Postgres via database/sql with pgx or lib/pq, or use an ORM like GORM or Bun. The database question is the same as for any backend stack: do you share a dev database or isolate per branch?
Option 1 — Shared dev database. Point all previews at one Postgres instance. Simplest setup, works well for read-heavy testing. The trade-off: two branches running conflicting migrations will step on each other.
Option 2 — Per-branch isolation. Use a database-branching service that creates lightweight Postgres clones in under a second, and have your service's startup create or connect to the branch database. Project variables apply to every preview, so the per-branch part lives in your startup code. Here's how your Go service reads its config:
// config.go
package config
import "os"
func DatabaseURL() string {
// Set DATABASE_URL under Project Settings → Variables
return os.Getenv("DATABASE_URL")
}
func Port() string {
if p := os.Getenv("PORT"); p != "" {
return p
}
return "8080"
}
Option 3 — SQLite for previews. For simpler Go services, use SQLite with modernc.org/sqlite (a pure-Go SQLite driver, no CGO required). This avoids external database dependencies entirely and works well for services that don't need Postgres-specific features.
Add a health check endpoint
Preview platforms need a health check to verify the container is ready. Add one to your Go HTTP server:
// health.go
package api
import (
"database/sql"
"encoding/json"
"net/http"
)
func HealthHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
err := db.Ping()
if err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(map[string]string{
"status": "unhealthy",
"error": err.Error(),
})
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"status": "ok",
})
}
}
Wire it into your router:
// main.go
mux.HandleFunc("/health", api.HealthHandler(db))
PreviewDrop polls /health after container start. When it returns 200, the preview is marked ready and the URL is posted to the PR.
Performance on Go
Go deploys benefit from Docker layer caching and Go's fast compile times. A warm redeploy goes through the same stages every time:
go mod download (cached)
go build
container start
stability poll
─────────────────────────────────
Total: typically about a minute, depending on project size
The first deploy on a branch (cold) takes 2–3 minutes — most of that is the initial go mod download and Docker layer pull. After that, every push rebuilds only the application code. The Go compiler's speed means the build stage is rarely the bottleneck.
Go-specific gotchas
CGO and static binaries. If your Go service uses CGO (for SQLite with mattn/go-sqlite3, for example), you need CGO_ENABLED=1 and a build image with gcc. The build handles this if PreviewDrop detects CGO dependencies in your go.mod.
go.sum must be committed. PreviewDrop runs go mod download during the build. If go.sum is in .gitignore, the build fails with a checksum mismatch. Run go mod tidy and commit go.sum before connecting your repo.
Port binding. Go's net/http defaults to localhost if you don't specify a host. Always bind to 0.0.0.0 in containerized environments:
// Correct — binds to all interfaces
log.Fatal(http.ListenAndServe("0.0.0.0:"+port, handler))
// Wrong — only accessible inside the container
log.Fatal(http.ListenAndServe("localhost:"+port, handler))
Graceful shutdown. Preview containers are stopped when the PR merges or the TTL expires. Handle SIGTERM so in-flight requests complete:
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
server := &http.Server{Addr: "0.0.0.0:" + port, Handler: handler}
go func() {
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
server.Shutdown(shutdownCtx)
When Go previews replace the staging server
A 4-person Go team at an infrastructure startup replaced their shared staging server with per-branch previews. Before: one staging instance, a Slack channel named #staging-lock, and an average 45-minute wait to test a branch. After: every PR gets its own URL, zero coordination, and the #staging-lock channel was archived.
The setup didn't add a single line of CI configuration. It didn't require Kubernetes. It didn't change the team's development workflow. It just gave every branch a URL.
For a deeper comparison of DIY vs managed approaches, read our GitHub Actions preview environment tutorial — it walks through building previews from scratch and the tradeoffs involved.
Start your first Go preview
PreviewDrop works with any Go service — from a single main.go API to a multi-service monorepo with shared packages. The free tier gives you 2 concurrent previews with no credit card required.
Want previews on your own pull requests? Start free — 2 concurrent previews, no credit card.