🚕 Analogy
Owning a car (a traditional server) means it sits in your driveway costing money whether you drive or not, and you handle the maintenance. Serverless is hailing a taxi: one appears when you need a ride, you pay only for the trip, and someone else owns and services the fleet. The catch is the brief wait for the cab to arrive (the cold start) — and a Go app is the rare passenger whose taxi shows up in milliseconds.
Serverless ≠ no servers
It means you don’t provision or manage them. You hand the platform your code or container; it handles capacity, scaling (including to zero), patching, and availability, and bills you per use instead of per always-on instance. Two flavors:
graph TD subgraph FaaS["FaaS (Lambda, Cloud Functions)"] F["one handler function<br/>platform packaging + limits<br/>event-triggered"] end subgraph SC["Serverless containers (Cloud Run, Fargate)"] C["any container that serves HTTP<br/>your normal Go server<br/>portable, fewer limits"] end
- FaaS — deploy a single handler function; the platform invokes it per event (HTTP, queue, schedule). Constraints on runtime, size, and duration.
- Serverless containers — deploy any container that serves HTTP — i.e. your ordinary Go web server. More portable, fewer limits. Often the most natural fit for Go.
See it: the handler model
Both models reduce to “a function that takes an event and returns a response.” You can write and test that handler as plain Go — no cloud needed. This runs here:
package main
import (
"encoding/json"
"fmt"
)
// A FaaS-style event and response — just structs.
type Event struct {
Name string `json:"name"`
}
type Response struct {
Message string `json:"message"`
}
// Handle is the function the platform invokes per event. It's stateless:
// everything it needs comes in via the event (and external stores).
func Handle(e Event) (Response, error) {
if e.Name == "" {
return Response{}, fmt.Errorf("name required")
}
return Response{Message: "hello, " + e.Name}, nil
}
func main() {
// Locally we just call it — the cloud runtime does this for you.
raw := []byte(`{"name":"Ada"}`)
var e Event
_ = json.Unmarshal(raw, &e)
resp, err := Handle(e)
if err != nil {
fmt.Println("error:", err)
return
}
out, _ := json.Marshal(resp)
fmt.Println(string(out)) // {"message":"hello, Ada"}
}
On AWS Lambda you’d wrap Handle with lambda.Start(Handle) from aws-lambda-go; on Cloud Run you’d serve it via net/http. The logic is the same testable Go function either way.
🐹 Go is one of the best serverless runtimes
Two Go traits make it shine here. Tiny, fast cold starts: a single static binary with no VM/interpreter to warm up starts in milliseconds, so scale-to-zero is practical and the pay-per-use math works. Low memory + high concurrency: goroutines let one serverless-container instance handle many concurrent requests cheaply (great on Cloud Run, which bills per-instance-time and can serve concurrent requests). For FaaS, use aws-lambda-go (or the provider’s SDK); for serverless containers, just build your normal tiny image and deploy it — no special framework. Initialize expensive things (DB pools, clients) once at package/init scope so they’re reused across warm invocations.
⚠️ Ephemeral, possibly-duplicated, and not always cheaper
Design for the model’s realities. Instances are ephemeral — don’t trust in-memory or local-disk state between calls; persist to a DB/cache/object store. Events are often at-least-once — the same invocation can arrive twice, so make handlers idempotent (idempotency keys, upserts) to avoid double effects. Mind limits — execution time, memory, and payload caps make long-running or heavy jobs a bad fit. And serverless is not universally cheaper: for sustained high traffic, always-on instances often cost less, and cold-start latency can be unacceptable on hot paths. Match serverless to spiky, event-driven, or low/variable-traffic work.
See also
- Dockerizing Go — the container serverless-container platforms run.
- Autoscaling — scale-to-zero and the cold-start trade-off.
- Why cloud-native Go — the static-binary properties that make Go serverless-friendly.
- aws-lambda-go & Cloud Run — a FaaS SDK and a serverless-container platform.
That’s the cloud-native extras — back to the Cloud-Native index, or explore the Architecture track.
Related topics
Packaging a Go service into a tiny, secure image — multi-stage builds, scratch/distroless bases, static linking, non-root users, and the version stamping that makes images traceable.
resilienceAutoscalingLetting the platform size your service to demand — horizontal vs vertical scaling, the Kubernetes HPA control loop and its replica formula, scaling on the right signal, and scale-to-zero.
containersWhy Cloud-Native GoWhat cloud-native means and why Go is its native language — static binaries in tiny containers, the 12-factor principles, and what 'production-ready' adds on top of 'it compiles'.
Check your understanding
Score: 0 / 51. What does 'serverless' actually mean?
Serverless doesn't mean no servers — it means you don't provision or manage them. You hand the platform your code (FaaS) or container (serverless containers); it handles capacity, scaling, patching, and availability, scaling out on demand and often to zero when idle. Billing is usage-based (per request / per 100ms of compute) rather than for always-on instances. The trade is less control for far less operational burden.
2. What's the difference between FaaS (e.g. AWS Lambda) and serverless containers (e.g. Cloud Run)?
FaaS (Lambda, Cloud Functions) runs an individual function behind a platform-specific invocation model and packaging, with constraints (execution time, package size, statelessness). Serverless containers (Cloud Run, Fargate, Container Apps) run any container that listens on a port and serves HTTP — so it's just your normal Go web server, portable across platforms, with fewer limits. For Go, serverless containers often feel the most natural; FaaS suits small, event-triggered functions.
3. What is a 'cold start' and why does Go handle it well?
A cold start is the extra latency when no warm instance exists and the platform must initialize one (load the runtime, your code, and dependencies) before serving. Languages that boot a heavy VM/interpreter (JVM, some Node setups) pay more. A compiled Go program is a single static binary with negligible startup and no runtime to warm up, so its cold starts are typically milliseconds — making Go an excellent serverless language, especially with scale-to-zero.
4. Why must serverless functions be stateless and idempotent-friendly?
The platform creates and destroys instances freely and may run many in parallel, so anything you stash in memory or local disk can vanish or not be shared — persistent state must live in a database/cache/object store. And because event sources often guarantee at-least-once delivery, the same event can arrive twice; handlers should be idempotent (use an idempotency key, upsert instead of insert) so a retry doesn't double-charge. Design for ephemeral, possibly-duplicated execution.
5. When is serverless a poor fit?
Serverless shines for spiky, event-driven, or low/variable-traffic workloads where pay-per-use and scale-to-zero save money and ops. It fits poorly for steady high-volume traffic (a constantly-busy service is often cheaper on reserved instances), hard real-time paths that can't absorb cold-start latency, long-running or compute-heavy jobs that hit execution-time/memory limits, and workloads needing large local state or persistent connections. Match the model to the traffic shape.
Comments
Sign in with GitHub to join the discussion.