Why Go is a joy to deploy
Go compiles to a single static binary. That means your production image can be a few megabytes — no runtime, no interpreter, no node_modules. A Gin API on a modern container platform starts in milliseconds and sips memory. Let's build a production-grade deploy.
Step 1: A Gin app that binds to $PORT
// main.go
package main
import (
"net/http"
"os"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
r.GET("/api/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "pong"})
})
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
r.Run("0.0.0.0:" + port)
}Step 2: Graceful shutdown
Gin's r.Run doesn't handle SIGTERM. For zero-dropped-requests on redeploy, run an http.Server and shut it down cleanly:
srv := &http.Server{Addr: "0.0.0.0:" + port, Handler: r}
go func() { srv.ListenAndServe() }()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
srv.Shutdown(ctx) // drains in-flight requestsStep 3: A tiny multi-stage Dockerfile
Build with the Go toolchain, ship on scratch (or distroless if you want a CA bundle and shell-free base):
FROM golang:1.23 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app .
FROM gcr.io/distroless/static-debian12
COPY --from=build /app /app
EXPOSE 8080
ENTRYPOINT ["/app"]CGO_ENABLED=0 produces a fully static binary; -ldflags="-s -w" strips debug info to shrink it. Distroless gives you CA certs (needed for HTTPS calls) without a full OS.
Step 4: Wire a managed Postgres
Use pgx and read the connection string from the environment. On PandaStack, attaching a managed PostgreSQL injects DATABASE_URL:
import "github.com/jackc/pgx/v5/pgxpool"
pool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatalf("db connect: %v", err)
}
defer pool.Close()pgxpool manages a connection pool — cap its max connections below your tier's limit (50 on free, 300 on Pro, 1000 on Premium).
Step 5: Deploy
Connect your repo. PandaStack auto-detects Go, or uses your Dockerfile. On push it builds with rootless BuildKit in a K8s Job pod, ships to Artifact Registry, and Helm-deploys:
git push origin mainLive build and app logs stream in real time. Add a custom domain for automatic SSL. Because the image is tiny, builds and cold starts are fast — handy on the free tier where apps scale to zero on spot nodes.
Step 6: Right-size compute
Go APIs are CPU-efficient and memory-light. The free tier (0.25 CPU / 512MB) handles a surprising amount of traffic for a Go service. Scale up to compute-optimized c1/c2 tiers for high-throughput APIs, or add replicas for horizontal scale.
Performance notes
- Use
gin.SetMode(gin.ReleaseMode)in production to drop debug logging overhead. - Reuse the
pgxpoolacross handlers — never open a connection per request. - A static-binary image cold-starts dramatically faster than interpreted runtimes, so scale-to-zero is far less painful for Go than for Node or Python.
References
- [Gin documentation](https://gin-gonic.com/docs/)
- [Go — Graceful shutdown (net/http)](https://pkg.go.dev/net/http#Server.Shutdown)
- [Distroless container images](https://github.com/GoogleContainerTools/distroless)
- [pgx — PostgreSQL driver](https://github.com/jackc/pgx)
---
A Go Gin API is about as lean as cloud deploys get — a few-MB image, millisecond starts, low memory. Push one to PandaStack's [free tier](https://dashboard.pandastack.io) and attach a managed Postgres; DATABASE_URL wires itself in and you're live.