Complete Guide: Deploying Go (Golang) to AWS Lambda
AWS Lambda is a powerful serverless platform, and Go is one of the best languages for it — fast cold starts, tiny binaries, and excellent performance. This guide covers everything you need to deploy Go code to Lambda.
Prerequisites
Before we start, make sure you have:
- Go 1.21+ installed (download)
- AWS CLI configured with credentials (setup guide)
- An AWS account with Lambda permissions
1. Understanding Lambda’s Go Runtime
AWS Lambda supports Go through the provided.al2023 runtime (Amazon Linux 2023). Your Go code compiles to a native binary that Lambda executes directly — no interpreter overhead.
Key points:
- Lambda expects a binary named
bootstrap - Must be compiled for Linux (
GOOS=linux) - Use ARM64 for better price/performance (
GOARCH=arm64)
2. Create Your Lambda Function
Create a new Go project:
1
2
mkdir my-lambda && cd my-lambda
go mod init my-lambda
Install the Lambda SDK:
1
go get github.com/aws/aws-lambda-go/lambda
Create main.go:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main
import (
"context"
"fmt"
"github.com/aws/aws-lambda-go/lambda"
)
// Request is the input to your Lambda
type Request struct {
Name string `json:"name"`
}
// Response is the output from your Lambda
type Response struct {
Message string `json:"message"`
}
// Handler is your Lambda function
func Handler(ctx context.Context, req Request) (Response, error) {
if req.Name == "" {
req.Name = "World"
}
return Response{
Message: fmt.Sprintf("Hello, %s!", req.Name),
}, nil
}
func main() {
lambda.Start(Handler)
}
3. Build for Lambda
Lambda requires a Linux binary. Build it with:
1
2
3
4
5
# For ARM64 (Graviton2 - recommended, cheaper)
GOOS=linux GOARCH=arm64 go build -tags lambda.norpc -o bootstrap main.go
# For x86_64 (if you need it)
GOOS=linux GOARCH=amd64 go build -tags lambda.norpc -o bootstrap main.go
Note: The -tags lambda.norpc flag reduces binary size by excluding unused RPC code.
Zip the binary:
1
zip function.zip bootstrap
4. Deploy with AWS CLI
Create an IAM Role (first time only)
Create a trust policy file trust-policy.json:
1
2
3
4
5
6
7
8
9
10
11
12
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
Create the role:
1
2
3
4
5
6
7
aws iam create-role \
--role-name lambda-go-role \
--assume-role-policy-document file://trust-policy.json
aws iam attach-role-policy \
--role-name lambda-go-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Create the Lambda Function
1
2
3
4
5
6
7
aws lambda create-function \
--function-name my-go-function \
--runtime provided.al2023 \
--architectures arm64 \
--role arn:aws:iam::YOUR_ACCOUNT_ID:role/lambda-go-role \
--handler bootstrap \
--zip-file fileb://function.zip
Replace YOUR_ACCOUNT_ID with your AWS account ID.
Update Existing Function
1
2
3
aws lambda update-function-code \
--function-name my-go-function \
--zip-file fileb://function.zip
5. Test Your Function
Invoke it from the CLI:
1
2
3
4
5
6
7
aws lambda invoke \
--function-name my-go-function \
--payload '{"name": "Gopher"}' \
--cli-binary-format raw-in-base64-out \
response.json
cat response.json
Output:
1
{"message":"Hello, Gopher!"}
6. Add an API Gateway (HTTP Endpoint)
To expose your Lambda as an HTTP API:
1
2
3
4
aws apigatewayv2 create-api \
--name my-go-api \
--protocol-type HTTP \
--target arn:aws:lambda:us-east-1:YOUR_ACCOUNT_ID:function:my-go-function
Update your handler to work with API Gateway:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
func Handler(ctx context.Context, request events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) {
name := request.QueryStringParameters["name"]
if name == "" {
name = "World"
}
body, _ := json.Marshal(map[string]string{
"message": fmt.Sprintf("Hello, %s!", name),
})
return events.APIGatewayV2HTTPResponse{
StatusCode: 200,
Headers: map[string]string{"Content-Type": "application/json"},
Body: string(body),
}, nil
}
func main() {
lambda.Start(Handler)
}
7. Using AWS SAM (Recommended for Production)
AWS SAM simplifies Lambda deployment. Create template.yaml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Timeout: 10
MemorySize: 128
Resources:
MyGoFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: my-go-function
CodeUri: .
Handler: bootstrap
Runtime: provided.al2023
Architectures:
- arm64
Events:
Api:
Type: HttpApi
Properties:
Path: /hello
Method: GET
Metadata:
BuildMethod: go1.x
Outputs:
ApiEndpoint:
Description: "API Gateway endpoint URL"
Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/hello"
Deploy with SAM:
1
2
sam build
sam deploy --guided
8. Makefile for Easy Builds
Create a Makefile:
1
2
3
4
5
6
7
8
9
10
11
12
13
.PHONY: build deploy clean
build:
GOOS=linux GOARCH=arm64 go build -tags lambda.norpc -o bootstrap main.go
zip function.zip bootstrap
deploy: build
aws lambda update-function-code \
--function-name my-go-function \
--zip-file fileb://function.zip
clean:
rm -f bootstrap function.zip
Now just run:
1
make deploy
9. Best Practices
Reduce Cold Starts
- Use ARM64 architecture (faster + cheaper)
- Keep dependencies minimal
- Initialize SDK clients outside the handler:
1
2
3
4
5
6
7
8
9
10
var dynamoClient *dynamodb.Client
func init() {
cfg, _ := config.LoadDefaultConfig(context.Background())
dynamoClient = dynamodb.NewFromConfig(cfg)
}
func Handler(ctx context.Context, req Request) (Response, error) {
// dynamoClient is already initialized
}
Error Handling
Return errors properly — Lambda will log them to CloudWatch:
1
2
3
4
5
6
func Handler(ctx context.Context, req Request) (Response, error) {
if req.ID == "" {
return Response{}, fmt.Errorf("missing required field: id")
}
// ...
}
Environment Variables
1
2
3
4
5
6
import "os"
func Handler(ctx context.Context, req Request) (Response, error) {
tableName := os.Getenv("DYNAMODB_TABLE")
// ...
}
Set them in Lambda configuration or SAM template.
Structured Logging
Use structured logging for better CloudWatch insights:
1
2
3
4
5
6
import "log/slog"
func Handler(ctx context.Context, req Request) (Response, error) {
slog.Info("processing request", "name", req.Name, "requestId", ctx.Value("requestId"))
// ...
}
10. Monitoring & Debugging
- CloudWatch Logs: All
fmt.Printandlogoutput goes here - X-Ray: Add tracing with
aws-xray-sdk-go - Lambda Insights: Enable for CPU/memory metrics
View logs:
1
aws logs tail /aws/lambda/my-go-function --follow
Conclusion
Go is an excellent choice for AWS Lambda — fast, efficient, and easy to deploy. The combination of tiny binaries, quick cold starts, and strong typing makes it ideal for serverless workloads.
Quick recap:
- Write your handler with
aws-lambda-go - Build for Linux with
GOOS=linux GOARCH=arm64 - Name the binary
bootstrap - Zip and deploy
Happy shipping! 🚀