This tutorial creates a classic AWS Lambda “Hello World” function using CloudFormation. Then triggers the function manually in order to test it.

About AWS Lambda and this Tutorial
AWS Lambda is a serverless compute service: run code without worrying about servers. You pay per execution, and get up to a million free executions a month.
Lambda functions are usually built to run in response to computing events. In this tutorial, we’ll just concentrate on creating a Lambda function. To test it, we’ll invoke it manually to simulate an event.
Click here to learn more about AWS Lambda functions and event triggers.
There are several ways to create Lambda functions, including: AWS Management Console, CloudFormation Command Line Interface (CLI), AWS CLI, and 3rd party tools.
This tutorial uses the CloudFormation CLI. We’ll write a template to create a neat little stack. Then delete the stack when done with it. Easy peasy.
Unfamiliar with AWS CloudFormation? Read this first:
What is CloudFormation?
Tutorial’s AWS Costs = None
Everything created in this tutorial is free:
Resource(s) | Cost | Free Tier Limits |
AWS Lambda | Free Tier, Always Free | 1,000,000 free requests per month Up to 3.2 million sec. compute time per month |
IAM (policy) | No Charge | The IAM service is offered at no additional charge. |
Pre-Requisites
- AWS Account (here for instructions on creating an account)
- AWS CLI installed on your machine (here for installation instructions)
Tutorial
Step 1: Write a Template
An AWS CloudFormation template includes, at a minimum, a description of the Resources we want provisioned in AWS.
Click here for more about Template Anatomy options.
For our Lambda function, we need 2 Resources: a role and a function.
Start with the Resources
header
Using your favorite editor, create a template file in YAML format named hello_stack.yml
(or, hello_stack.json
if you prefer JSON.)
Add the Resources
header:
YAML
Resources:
JSON
{
"Resources" : {
}
}
Done! Now define the role …
IAM Role for Lambda
In order for the Lambda to function, you need to grant it the necessary privileges. It may seem weird to grant privileges to … a chunk of code … but that’s how it works.
We first create a role, giving it the privileges, then grant the role to the Lambda function.
Add this to your template file, under Resources: (be sure to preserve indentations on each line):
YAML
HelloLambdaRole:
Type: AWS::IAM::Role
Properties:
RoleName: HelloLambdaRole
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
JSON
"HelloLambdaRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": "HelloLambdaRole",
"AssumeRolePolicyDocument": {
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
}
},
Read it Like a Human

For more about AWS::IAM::Role Properties, click here.
Done! Now define the function …
Lambda Function
Now we get to the good part: the Lambda function itself. Add this YAML (or JSON) code to your template file under Resources (it does not matter in what order you declare your resources, but it may make more sense to the reader if you put the role first and the function second):
YAML
HelloLambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: HelloLambdaFunction
Role: !GetAtt HelloLambdaRole.Arn
Runtime: python3.7
Handler: index.my_handler
Code:
ZipFile: |
def my_handler(event, context):
message = 'Hello Lambda World!'
return message
JSON
"HelloLambdaFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"FunctionName": "HelloLambdaFunction",
"Role": {
"Fn::GetAtt": ["HelloLambdaRole","Arn"]
},
"Runtime": "python3.7",
"Handler": "index.my_handler",
"Code": {
"ZipFile": "def my_handler(event, context):\n message = \"Hello Lambda World!\"\n return message\n"
}
}
}
Read it Like a Human

Here are a few more details about the Lambda function properties:
- The Resource is named “HelloLambdaFunction”. We can use any alphanumeric (A-Za-z0-9) we like as long as it’s unique to the template.
FunctionName
does not have to be the same as the Resource‘s name, but it’s cleaner to keep them the same. TheFunctionName
is what’s stored within your AWS account. If you do not specify a name here, AWS will generate one for you.Role
is where the function gets its privileges, and it is required. You created the HelloLambdaRole role so you could supply it here. CloudFormation wants the role’s ARN (a long string AWS assigns each resource), not the friendly name you gave it. Since we don’t know the role’s ARN because it hasn’t been created yet, we use a handy intrinsic function:!GetAtt
(‘Get Attribute’), supplying it your friendly name and the attribute keyword Arn. The intrinsic function will return our new role’s ARN for us. There are various ways to call the intrinsic function. In the above example, the YAML used:!GetAtt HelloLambdaRole.Arn
and JSON used:"Fn::GetAtt": ["HelloLambdaRole","Arn"]
.Runtime
is the function’s programming language, and you have a set to pick from. Click here for valid Runtime values.- The
Handler
specifies what function, within the Lambda code, should be invoked. Here,index.my_handler
means invoke the my_handler function in the file named index (see next bullet point). Zipfile:
means you are supplying the Runtime code ‘inline’ (within this template, not an external file.) Since you’re using ‘inline’ code, CloudFormation will zip your code for you, storing it in a file namedindex
. This is why theHandler
value you supplied above wasindex.myhandler
(<filename>.<runtime_function_name>).
For more about Lambda Function Properties, click here.
Your template is done! Here’s what it should look like all put together:
YAML
Resources:
HelloLambdaRole:
Type: AWS::IAM::Role
Properties:
RoleName: HelloLambdaRole
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
HelloLambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: HelloLambdaFunction
Role: !GetAtt HelloLambdaRole.Arn
Runtime: python3.7
Handler: index.my_handler
Code:
ZipFile: |
def my_handler(event, context):
message = 'Hello Lambda World!'
return message
JSON
{
"Resources" : {
"HelloLambdaRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": "HelloLambdaRole",
"AssumeRolePolicyDocument": {
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
}
},
"HelloLambdaFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"FunctionName": "HelloLambdaFunction",
"Role": {
"Fn::GetAtt": ["HelloLambdaRole","Arn"]
},
"Runtime": "python3.7",
"Handler": "index.my_handler",
"Code": {
"ZipFile": "def my_handler(event, context):\n message = \"Hello Lambda World!\"\n return message\n"
}
}
}
}
}
You might be wondering, what is the point of granting a role to the Lambda function when its only privileges are that Lambda can run a Lambda Function? Consider that our “Hello World” function is not interacting with any other resources, so it really does not need any more privileges. If the function, for example, wrote to a log file or sent out a message or updated a database, then the function would need to be granted more privileges (via the role) to have access to those relevant resources (log file, SNS, database.)
Step 2. Create the Stack
Run CloudFormation to create the stack using your template. Type or copy this at your terminal prompt:
YAML
aws cloudformation create-stack --stack-name hello-lambda-stack \
--template-body file://hello_stack.yml \
--capabilities CAPABILITY_NAMED_IAM
JSON
aws cloudformation create-stack --stack-name hello-lambda-stack \
--template-body file://hello_stack.json \
--capabilities CAPABILITY_NAMED_IAM
It may take AWS a few minutes to create the stack. Check its status by entering this at your terminal prompt:
aws cloudformation list-stacks \
--query 'StackSummaries[*].[StackName, StackStatus]' \
--stack-status-filter CREATE_IN_PROGRESS CREATE_COMPLETE \
--output table
Wait for the status to be CREATE_COMPLETE before continuing.
Step 3. Invoke the Lambda Function
Test the new Lambda function by manually invoking it, to simulate an event:
aws lambda invoke \
--invocation-type RequestResponse \
--function-name HelloLambdaFunction \
--log-type Tail outputfile.txt; more outputfile.txt
Your output should look something like this:
{
"StatusCode": 200,
"LogResult": "U1RBUlQgUmVxdWVzdElkOiAxNjEzMGZlMy03YmNkLTQyZGMtYmY2NS05MmNhMjllNDdhNTkgVmVyc2lvbjogJExBVEVTVApFTkQgUmVxdWVzdElkOiAxNjEzMGZlMy03YmNkLTQyZGMtYmY2NS05MmNhMjllNDdhNTkKUkVQT1JUIFJlcXVlc3RJZDogMTYxMzBmZTMtN2JjZC00MmRjLWJmNjUtOTJjYTI5ZTQ3YTU5CUR1cmF0aW9uOiAxLjM0IG1zCUJpbGxlZCBEdXJhdGlvbjogMTAwIG1zCU1lbW9yeSBTaXplOiAxMjggTUIJTWF4IE1lbW9yeSBVc2VkOiA1NiBNQgkK",
"ExecutedVersion": "$LATEST"
}
"Hello Lambda World!"
A Status code in the 200 range means it was a successful request.
Congratulations! You’ve successfully created an AWS Lambda Function and simulated an event to trigger it!
You may keep the function. Unless you’re going to invoke it more than a million times a month, it is free to have in your AWS account.
Step 4. Delete the Stack (optional)
Or, you can delete the stack, which will delete everything you created in this tutorial. Enter this at your terminal prompt:
aws cloudformation delete-stack --stack-name hello-lambda-stack
Conclusion
You now know the basic components for creating a Lambda Function using CloudFormation from the command line.
In the “real world” of Lambda and serverless applications, the functions you create will be triggered by an AWS (or remote) resource or application.
What Next?
Next step is to use your new Lambda Function skills and develop a serverless application that will trigger Lambda functions for you. For some ideas on how to start, click here.
Reference
Template Resource Section Structure
AWS::Lambda::Function Properties
AWS Lambda Function Handler in Python
This is one of the nicest tutorials I have seen. I hope you create more tutorials on AWS!
Thank you for the encouraging words, Brent! I will take it to heart and certainly write more tutorials ๐