Serverless computing has revolutionized how we deploy and scale applications. AWS Lambda, as a leading serverless platform, offers multiple ways to deploy your applications. In this post, we’ll explore three different Scenarioes to deploy a simple webapp to AWS Lambda using Serverless Framework:
- Python with ZIP deployment
- Java with JAR deployment
- Go with Docker container
Prerequisites Link to heading
Before we begin, ensure you have:
- AWS CLI installed and configured
- Appropriate AWS permissions
- Python 3.11+ (for Python examples)
- Java 11+ (for Java example)
- Go 1.21+ (for Go example)
- Docker (for container scenario)
- Node.js and npm (for Serverless Framework)
- Serverless Framework installed (
npm install -g serverless@3.39.0) - AWS credentials for serverless
# Configure AWS credentials for serverless using environment variables
serverless config credentials --provider aws --key $AWS_ACCESS_KEY_ID --secret $AWS_SECRET_ACCESS_KEY
✔ Profile "default" has been configured
Scenario 1: Python with ZIP Deployment Link to heading
This is the simplest scenario, perfect for small Python applications.
Project Structure Link to heading
python-zip/
├── app.py
├── requirements.txt
└── serverless.yml
app.py Link to heading
import json
def lambda_handler(event, context):
return {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json'
},
'body': json.dumps({
'message': 'Hello from Python Lambda!'
})
}
requirements.txt Link to heading
boto3==1.26.137
serverless.yml Link to heading
service: python-zip-app
provider:
name: aws
runtime: python3.11
region: us-east-1
package:
individually: true
functions:
hello:
handler: app.lambda_handler
package:
artifact: deployment.zip
events:
- http:
path: /hello
method: get
Deployment Steps Link to heading
# Create deployment package
pip install -r requirements.txt -t ./package
cp app.py ./package/
cd package
zip -r ../deployment.zip .
cd ..
# Deploy
serverless deploy --stage dev
Deploying python-zip-app to stage dev (us-east-1)
✔ Service deployed to stack python-zip-app-dev (79s)
endpoint: GET - https://jzlki1fb8e.execute-api.us-east-1.amazonaws.com/dev/hello
functions:
hello: python-zip-app-dev-hello (31 MB)
# Visit the deployed endpoint
curl https://jzlki1fb8e.execute-api.us-east-1.amazonaws.com/dev/hello
{"message": "Hello from Python Lambda!"}
# Tear down
serverless remove --stage dev
Notes: If you don’t want to pre build the deployment ZIP, you can use the serverless-python-requirements plugin to handle dependencies automatically. This approach is particularly useful when:
- You have complex dependencies
- You need to compile native extensions
- You want to ensure consistent builds across environments
Here’s how to configure it:
plugins:
- serverless-python-requirements
custom:
pythonRequirements:
dockerizePip: true # Use Docker to build dependencies
slim: true # Remove unnecessary files
noDeploy: [] # Exclude packages from deployment
useStaticCache: true # Cache dependencies between builds
functions:
hello:
handler: app.lambda_handler
events:
- http:
path: /hello
method: get
Scenario 2: Java with JAR Deployment Link to heading
This scenario deploys a Java application that needs the full power of the JVM.
Project Structure Link to heading
java-jar/
├── src/
│ └── main/
│ └── java/
│ └── com/
│ └── example/
│ └── App.java
├── pom.xml
└── serverless.yaml
App.java Link to heading
package com.example;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
response.setStatusCode(200);
response.setBody("{\"message\": \"Hello from Java Lambda!\"}");
return response;
}
}
pom.xml Link to heading
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>lambda-java</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>3.11.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
</project>
serverless.yaml Link to heading
service: java-jar-app
provider:
name: aws
runtime: java11
region: us-east-1
package:
individually: true
functions:
hello:
handler: com.example.App::handleRequest
package:
artifact: target/lambda-java-1.0-SNAPSHOT-jar-with-dependencies.jar
events:
- http:
path: /hello
method: get
Deployment Steps Link to heading
# Build the JAR
mvn clean package
# Deploy
serverless deploy --stage dev
Deploying java-jar-app to stage dev (us-east-1)
✔ Service deployed to stack java-jar-app-dev (29s)
endpoint: GET - https://rjk8p44n91.execute-api.us-east-1.amazonaws.com/dev/hello
functions:
hello: java-jar-app-dev-hello (1 MB)
# Visit the deployed endpoint
curl https://rjk8p44n91.execute-api.us-east-1.amazonaws.com/dev/hello
{"message": "Hello from Java Lambda!"}
# Tear down
serverless remove --stage dev
Scenario 3: Go with Docker Container Link to heading
This scenario offers the most flexibility and control over the runtime environment, with the added benefit of Go’s performance and small binary size.
Project Structure Link to heading
go-docker/
├── main.go
├── go.mod
├── Dockerfile
└── serverless.yaml
main.go Link to heading
package main
import (
"context"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
type Response events.APIGatewayProxyResponse
func HandleRequest(ctx context.Context) (Response, error) {
response := Response{
StatusCode: 200,
Headers: map[string]string{
"Content-Type": "application/json",
},
Body: `{"message": "Hello from Go Lambda in Docker!"}`,
}
return response, nil
}
func main() {
lambda.Start(HandleRequest)
}
go.mod Link to heading
module go-lambda
go 1.21
require (
github.com/aws/aws-lambda-go v1.41.0
)
Dockerfile Link to heading
FROM public.ecr.aws/lambda/provided:al2
RUN yum install -y golang
WORKDIR /app
COPY go.mod go.sum main.go ./
RUN go mod download
RUN go build -o main
ENTRYPOINT [ "/app/main" ]
serverless.yaml Link to heading
service: go-docker-app
provider:
name: aws
region: us-east-1
ecr:
images:
appimage:
uri: ${env:AWS_ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/go-lambda:latest
functions:
hello:
image:
name: appimage
architecture: arm64
events:
- http:
path: /hello
method: get
Deployment Steps Link to heading
#
go mod tidy
# Create ECR and login
export AWS_ACCOUNT_ID='<your-account-id>'
aws ecr create-repository --repository-name go-lambda --region us-east-1
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com
# Build and push the Docker image
docker build -t go-lambda .
docker tag go-lambda:latest $AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/go-lambda:latest
docker push $AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/go-lambda:latest
# Deploy
serverless deploy --stage dev
Deploying go-docker-app to stage dev (us-east-1)
✔ Service deployed to stack go-docker-app-dev (109s)
endpoint: GET - https://dm54x42wrk.execute-api.us-east-1.amazonaws.com/dev/hello
functions:
hello: go-docker-app-dev-hello
# Visit the deployed endpoint
curl https://dm54x42wrk.execute-api.us-east-1.amazonaws.com/dev/hello
{"message": "Hello from Go Lambda in Docker!"}
# Tear down
serverless remove --stage dev
# Remove ECR repository and images
aws ecr delete-repository --repository-name go-lambda --region us-east-1 --force
Comparison Link to heading
| Scenario | Pros | Cons | Best For |
|---|---|---|---|
| Python ZIP | - Simple deployment - Fast cold starts - Small package size | - Limited dependencies - No custom runtime | Small to medium applications Quick prototypes Script-like functions |
| Java JAR | - Full JVM capabilities - Strong typing - Enterprise features | - Larger package size - Slower cold starts | Enterprise applications Complex business logic Existing Java codebases |
| Go Docker | - Full control over runtime - Any dependencies - Consistent environment - Excellent performance | - More complex setup - Larger image size | Performance-critical applications Custom runtime needs Microservices |
Cold starts are a crucial consideration when working with AWS Lambda. They occur when:
- A function is invoked for the first time
- After a period of inactivity (typically 5-15 minutes)
- When Lambda needs to scale to handle increased load
What Happens During a Cold Start Link to heading
- Container Initialization: Lambda creates a new container
- Runtime Setup: The runtime environment (Python, Java, Go) is initialized
- Code Loading: Your function code and dependencies are loaded
- Initialization: Any initialization code in your function runs
- Request Handling: Finally, your function can process the actual request
Strategies to Minimize Cold Starts Link to heading
Provisioned Concurrency
functions: hello: provisionedConcurrency: 5 # Keep 5 instances warmOptimize Package Size
- Minimize dependencies
- Use slim packages
- Remove unused code
Warm-up Strategies
- Regular pings to keep functions warm
- CloudWatch Events to trigger functions periodically
- Use provisioned concurrency for critical paths
Architecture Considerations
- Use appropriate memory settings
- Consider function timeout settings
- Implement proper error handling