Updated Nov 5, 20267 min read

AWS Lambda with Go in 2026: Setup, Cold Starts and When to Use It

How to build and package a Go Lambda on provided.al2023, what drives cold starts, when to pick Function URLs or API Gateway, and when an always-on Go API with Postgres and queues is a better fit.

Go on AWS Lambda is a compiled binary behind the Lambda programming model, not a managed language runtime like Node.js. You build for Linux, name the executable bootstrap, and deploy it on provided.al2023 (or provided.al2). That path is fast and cheap for short, spiky work, and awkward once the same backend needs a connection pool, a queue consumer and a schedule that must not stop at 15 minutes.

This guide covers setup, packaging, cold starts, HTTP front doors and when to leave Lambda for always-on containers. It ends with Encore (infrastructure from code) as a Go alternative that keeps APIs, Postgres, queues and cron in one codebase without claiming to run on Lambda.

AWS Lambda with Go at a glance

TopicWhat to use
Runtimeprovided.al2023 (preferred) or provided.al2; not go1.x
Librarygithub.com/aws/aws-lambda-go/lambda
Binary namebootstrap at the root of the zip
Architecturesarm64 or x86_64
Max duration15 minutes (900 seconds) per invocation
Memory128 MB to 10,240 MB
HTTPFunction URL or API Gateway
Always-on Go API + infraEncore on Fargate in your AWS account

Set up a Go handler

Initialize a module and pull the Lambda Go library. Handlers are ordinary functions that lambda.Start wires up. Clients and other heavy setup belong in init() or package scope so warm invocations reuse them:

~
$ go mod init example.com/receipts$ go get github.com/aws/aws-lambda-go/lambda
package main import ( "context" "encoding/json" "log" "github.com/aws/aws-lambda-go/lambda" ) type Order struct { OrderID string `json:"order_id"` Amount float64 `json:"amount"` } func handleRequest(ctx context.Context, raw json.RawMessage) error { var order Order if err := json.Unmarshal(raw, &order); err != nil { return err } log.Printf("processed order %s amount %.2f", order.OrderID, order.Amount) return nil } func main() { lambda.Start(handleRequest) }

Valid signatures take zero to two arguments (optional context.Context first) and return zero to two values (optional error last), as listed in the handler docs. Event structs live in github.com/aws/aws-lambda-go/events when you integrate with API Gateway, SQS or S3.

Package for provided.al2023

Go does not need a language runtime on Lambda. Per the deployment package guide, cross-compile a Linux binary named bootstrap, put it at the root of the zip, and create the function with --runtime provided.al2023. The lambda.norpc build tag drops RPC code that only the deprecated go1.x runtime needed, which shrinks the zip:

~
$ GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -tags lambda.norpc -o bootstrap main.go$ zip function.zip bootstrap$ aws lambda create-function --function-name receipts \--runtime provided.al2023 --handler bootstrap \--architectures arm64 \--role arn:aws:iam::111122223333:role/lambda-ex \--zip-file fileb://function.zip

The unzipped deployment package must stay under 250 MB. Prefer arm64 when you can: Graviton is the path AWS documents alongside provided.al2023, and smaller binaries usually mean less time spent pulling code on a cold start.

Cold starts in practice

A cold start is the time to download the package, start the environment and run package init before your handler runs. Go's baseline is often low because there is no language VM to boot, but the work you do in init dominates: loading the AWS SDK, opening TLS to other services, and reading large config all add up. Keep initialization outside the handler so warm invokes skip it, and keep the zip small.

Provisioned concurrency removes cold starts for a reserved number of environments at a steady cost. SnapStart is not the Go story the way it is for Java. Memory also buys CPU on Lambda (roughly one vCPU equivalent at 1,769 MB), so undersized functions can look "cold" when they are really CPU-starved on the first request. Measure with your own package and dependencies rather than a hello-world number from a blog.

Function URL vs API Gateway

A Lambda function URL is an HTTPS endpoint of the form https://<url-id>.lambda-url.<region>.on.aws. Auth is AWS_IAM or NONE, with optional CORS. The URL is public-internet only; PrivateLink does not apply to function URLs. Reserved concurrency caps concurrency and, through the function URL, effective request rate (about 10 RPS per unit of reserved concurrency).

API Gateway adds stages, authorizers, usage plans, request validation, mapping templates and broader integration patterns. Use it when the HTTP edge needs those controls. Use a Function URL when the function is the product and you want the shortest path from browser or webhook to Go.

Neither front door changes Lambda's 15-minute limit, 6 MB synchronous payload cap, or the one-connection-per-concurrent-invocation pattern that makes pooled Postgres painful without RDS Proxy.

When Lambda fits vs always-on containers

Lambda fits event handlers and APIs with spiky or low traffic that finish well inside 15 minutes, where paying per request and scaling to zero matter more than a warm process. S3 object processing, webhooks, fan-out from SQS and light CRUD behind a Function URL are the usual wins for Go.

Always-on containers (ECS on Fargate, or similar) fit services that hold database pools, WebSockets or long jobs, and backends that are several APIs plus workers plus schedules. Glue on Lambda multiplies: one function per trigger, an event source mapping per queue, IAM between each pair, and local testing through SAM or emulators. A Go service model pays off when the handler stays warm and keeps Postgres, queues and cron as first-class resources rather than bolt-on services.

How do you run Go APIs with Postgres, queues and cron without Lambda?

Encore

Encore combines an open source infrastructure SDK for TypeScript and Go with a platform that provisions and deploys into your own AWS or GCP account. On AWS, services run on Fargate (or EKS), not on Lambda. You keep writing Go; you stop wiring one Lambda per trigger for the application resources you own.

The Infrastructure page in the Encore dashboard with a database's configuration panel open, showing fields for vCPUs, memory, storage, availability type, max connections and backups.
Each provisioned resource can be reviewed and its configuration edited per environment in the Encore dashboard. The screenshot shows a GCP Cloud SQL database.

Set up the app

Get started
brew install encoredev/tap/encore &&
encore app create
Copy

Run in your terminal to get started locally.

~
$ encore app create orders --example=go/hello-world$ cd orders

An orders service with a public API, Postgres, a Pub/Sub worker and a hourly sweep looks like this with released Go APIs only:

// order/order.go package order import ( "context" "encore.dev/cron" "encore.dev/pubsub" "encore.dev/rlog" "encore.dev/storage/sqldb" ) var db = sqldb.NewDatabase("orders", sqldb.DatabaseConfig{ Migrations: "./migrations", }) type CreateParams struct { SKU string `json:"sku"` } type CreateResponse struct { ID int64 `json:"id"` } type OrderCreated struct { ID int64 `json:"id"` SKU string `json:"sku"` } var OrderCreatedTopic = pubsub.NewTopic[*OrderCreated]("order-created", pubsub.TopicConfig{ DeliveryGuarantee: pubsub.AtLeastOnce, }) //encore:api public method=POST path=/orders func Create(ctx context.Context, p *CreateParams) (*CreateResponse, error) { var id int64 err := db.QueryRow(ctx, ` INSERT INTO orders (sku) VALUES ($1) RETURNING id `, p.SKU).Scan(&id) if err != nil { return nil, err } if _, err := OrderCreatedTopic.Publish(ctx, &OrderCreated{ID: id, SKU: p.SKU}); err != nil { return nil, err } return &CreateResponse{ID: id}, nil } var _ = pubsub.NewSubscription( OrderCreatedTopic, "fulfill-order", pubsub.SubscriptionConfig[*OrderCreated]{ Handler: func(ctx context.Context, e *OrderCreated) error { rlog.Info("fulfilling order", "id", e.ID, "sku", e.SKU) return nil }, }, ) var _ = cron.NewJob("sweep-stale-orders", cron.JobConfig{ Title: "Sweep stale orders", Every: 1 * cron.Hour, Endpoint: SweepStale, }) //encore:api private func SweepStale(ctx context.Context) error { rlog.Info("sweeping stale orders") return nil }
-- order/migrations/1_create_orders.up.sql CREATE TABLE orders ( id BIGSERIAL PRIMARY KEY, sku TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() );

encore run starts Postgres locally and runs the API and Pub/Sub handler, per the Go SQL database and Pub/Sub docs. Cron jobs do not run locally; call SweepStale manually to test it, as noted in the cron docs. On deploy, Encore creates the Fargate service, RDS database, SNS topic and SQS subscription with a dead-letter queue, and provisions an IAM role for the service. There is no bootstrap zip, Function URL or API Gateway mapping per handler. For AWS APIs Encore does not model, keep using the AWS SDK for Go v2 inside the same service. APIs themselves use //encore:api as in the defining APIs guide.

An AI coding agent can add a column, a subscription or a cron endpoint in one change and verify it locally before you push.

Best fit: Go teams who outgrew one-function-per-trigger on Lambda and want a typed API with Postgres, queues and cron in their own AWS account.

Frequently asked questions

Which runtime should I use for Go on AWS Lambda?

Use an OS-only runtime in the provided family, usually provided.al2023. The old go1.x managed runtime is deprecated. Go compiles to a native binary, so Lambda does not ship a separate Go language runtime.

How do I package a Go Lambda function?

Build a Linux binary named bootstrap with GOOS=linux and GOARCH set to arm64 or amd64, optionally with -tags lambda.norpc, zip that file at the archive root, and create the function with runtime provided.al2023 and handler bootstrap.

When should I use a Lambda Function URL instead of API Gateway?

Use a Function URL for a simple HTTPS endpoint with IAM or open auth and optional CORS, when you do not need API Gateway features such as usage plans, request validation, or complex routing. Use API Gateway when those controls matter.

How do I build a Go API with Postgres, a queue and a cron job without Lambda handlers for each trigger?

With Encore, declare endpoints with //encore:api, a database with sqldb.NewDatabase, a topic with pubsub.NewTopic and a cron job with cron.NewJob. Postgres runs locally during development, and on deploy to your AWS account Encore runs the services on Fargate and provisions the resources, including an IAM role for the service.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders