GCP Cloud Run & AWS Fargate Patterns
Container platforms let Go teams run a normal main with net/http or gRPC while the cloud manages scaling, patching, and load balancing.
Search across all documentation pages
Container platforms let Go teams run a normal main with net/http or gRPC while the cloud manages scaling, patching, and load balancing.
Cloud Run optimizes for request-driven HTTP with scale to zero; AWS Fargate runs ECS tasks without managing EC2 - often for steady services rather than idle-to-zero economics.
Ship a minimal OCI image with a static Go binary, listen on $PORT, and configure platform health checks.
Cloud Run sets max concurrent requests per instance - tune it against handler latency and DB pool size.
Fargate tasks need CPU/memory in the task definition, awsvpc networking, and optional Service Connect for service mesh-style discovery.
Both platforms benefit from SIGTERM-aware graceful shutdown in Go.
Quick-reference recipe card - copy-paste ready.
srv := &http.Server{
Addr: ":" + port,
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
}
go func() {
<-shutdown
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
_ = srv.Shutdown(ctx)
}()
log.Fatal(srv.ListenAndServe())# Cloud Run deploy sketch
gcloud run deploy api --image IMAGE --region REGION --concurrency 80 --cpu 1 --memory 512MiWhen to reach for this:
docker run.package main
import (
"context"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
mux := http.NewServeMux()
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
})
mux.HandleFunc("GET /readyz", func(w http.ResponseWriter, r *http.Request) {
// optional: ping database with short timeout
w.WriteHeader(http.StatusNoContent)
})
mux.HandleFunc("GET /api/status", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"status":"ok"}`))
})
srv := &http.Server{
Addr: ":" + port,
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
}
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGTERM, syscall.SIGINT)
go func() {
<-stop
slog.Info("shutdown started")
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
slog.Error("shutdown error", "err", err)
}
}()
slog.Info("listening", "addr", srv.Addr)
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
slog.Error("server error", "err", err)
os.Exit(1)
}
}What this demonstrates:
/healthz) and readiness (/readyz) for orchestrators.ReadHeaderTimeout on the server for slow-client protection.Shutdown - Cloud Run sends SIGTERM before killing the container.| Setting | Effect on Go service |
|---|---|
--concurrency | Max simultaneous requests per instance; raise when handlers are I/O bound |
--cpu / --memory | More CPU speeds JSON and TLS; memory affects GC headroom |
--min-instances | Reduces cold starts at baseline cost |
--timeout | Request deadline enforced at the proxy |
| VPC connector | Private RFC1918 access to Cloud SQL / Memorystore |
{
"family": "go-api",
"cpu": "512",
"memory": "1024",
"networkMode": "awsvpc",
"containerDefinitions": [{
"name": "api",
"image": "ACCOUNT.dkr.ecr.REGION.amazonaws.com/go-api:latest",
"portMappings": [{"containerPort": 8080}],
"healthCheck": {
"command": ["CMD-SHELL", "wget -q -O- http://127.0.0.1:8080/healthz || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3
},
"stopTimeout": 30
}]
}Match container stopTimeout with your Go Shutdown budget so ECS does not SIGKILL mid-drain.
| Platform | Idle cost | Cold start | Best fit |
|---|---|---|---|
| Cloud Run | Near zero at scale-to-zero | Container start + Go init | Bursty HTTP |
| Fargate (desired >= 1) | Steady task hours | None while tasks run | Workers, stable RPC |
| Lambda | Zero between invokes | Binary load + init | Event triggers |
:8080 ignoring PORT - Cloud Run injects PORT; binding wrong port fails deploy health checks. Fix: Read os.Getenv("PORT") with fallback.signal.Notify + srv.Shutdown.MaxOpenConns=10 causes waits or timeouts. Fix: Align Cloud Run concurrency with pool math./readyz only, keep /healthz cheap.| Alternative | Use When | Don't Use When |
|---|---|---|
| AWS Lambda | Event spikes, short handlers | Long HTTP streaming sessions |
| GKE / EKS | Operators, complex networking | Simple single-service APIs |
| App Engine standard | Legacy GCP PaaS standards | You already standardized on Cloud Run |
| EC2 + systemd | Full host control required | You want managed patching |
Cloud Run supports gRPC on HTTP/2 with TLS.
For internal east-west traffic, many teams still use REST or connect via VPC to GKE gRPC backends.
Use smaller images, set --min-instances for baseline traffic, and avoid heavy init() work.
Measure with Cloud Trace before over-provisioning CPU.
You do not manage EC2, but you still choose task size and desired count.
It is "serverless capacity" rather than scale-to-zero functions.
Yes if the process listens on a configurable port and reads config from environment variables.
IaC differs per cloud, not the Go binary.
When you need always-on tasks, longer runtimes without API Gateway limits, or tight integration with ECS service discovery and AWS networking primitives.
gcloud run deployhttp.Server tuningStack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 16, 2026