A guide for deploying CloudFormation with CLI using Rain
Last year, I wrote a post describing the difference between aws cloudformation deploy and aws cloudformation create-stack. I concluded that the easiest method for deploying CloudFormation templates was cloudformation deploy since it handled change sets on your behalf.
My opinion has changed; I discovered a new tool named Rain that is maintained by AWS.
What is Rain
For folks needing to deploy raw CloudFormation templates, rain is your hero. The tool brings you a lot of power:
- Allows input parameters
- Showcases real-time updates for stack deployments
- Filters deployment logs sensibly
- Provides the ability to generate CloudFormation templates via AI
- Manipulates stack sets
- Formats template files
And many more features! As a previous user of CloudFormation, this tool appears amazing.
Installing Rain
There are a few options for installing Rain, but I’ll use the golang install for this tutorial.
1go install github.com/aws-cloudformation/rain/cmd/rain@latestFor MacOS users, brew is easy:
1brew install rainThe remaining releases are featured in their GitHub repository.
Build CloudFormation stacks using Rain
The build command accepts a prompt parameter to generate CloudFormation templates using AI. Let’s use it for generating a CloudFormation template with a S3 bucket.
1rain build -p "A s3 bucket that is secure"Output:
1AWSTemplateFormatVersion: 2010-09-09
2Description: Create an S3 bucket
3
4Resources:
5 MyS3Bucket:
6 Type: AWS::S3::Bucket
7 Properties:
8 BucketName: my-secure-bucket
9 AccessControl: Private
10 VersioningConfiguration:
11 Status: EnabledAlthough the AccessControl property is a considered legacy, it’s great that the template was generated on my behalf!
In addition, the build property will generate a skeleton for resources as well:
1rain build AWS::Route53::CidrCollectionor
1rain build CidrCollectionThis outputs a template with all the parameters and dummy CHANGEME values for a Route53 CIDR collection:
1AWSTemplateFormatVersion: "2010-09-09"
2
3Description: Template generated by rain
4
5Resources:
6 MyCidrCollection:
7 Type: AWS::Route53::CidrCollection
8 Properties:
9 Locations:
10 - CidrList:
11 - CHANGEME
12 LocationName: CHANGEME
13 Name: CHANGEME
14
15Outputs:
16 MyCidrCollectionArn:
17 Value: !GetAtt MyCidrCollection.Arn
18
19 MyCidrCollectionId:
20 Value: !GetAtt MyCidrCollection.IdIf you prefer json, there’s a flag for that:
1rain build CidrCollection --jsonIf you supply the command an ambigious term like “collection”, it provides you a warning:
1rain build collectionOutput:
1Ambiguous resource type 'collection'; could be any of:
2 AWS::DevOpsGuru::ResourceCollection
3 AWS::Location::GeofenceCollection
4 AWS::OpenSearchServerless::Collection
5 AWS::Rekognition::Collection
6 AWS::Route53::CidrCollectionBootstrap using Rain
Before deploying stacks into an environment, let’s run the bootstrap command:
1rain bootstrapOutput:
1Rain needs to create an S3 bucket called 'rain-artifacts-012345678901-us-east-1'. Continue? (Y/n)This command creates an artifacts bucket that rain references when deploying stacks.
It has the following naming convention:
1rain-artifacts-{accountId}-{regionName}Deploying stacks using Rain
In this section, we’ll explore deploying templates with rain.
Deploying an S3 bucket
Using the S3 template rain build generated, let’s deploy it!
This is my current directory structure:
1project
2└── s3.ymlThis is the command I’ll use to deploy the s3.yml with a stack name of s3-bucket-stack:
1rain deploy s3.yml s3-bucket-stackIf rain bootstrap was not executed before deploying, it’ll prompt you to do so.
My deployment failed with the following message:
1CloudFormation will make the following changes:
2Stack s3-bucket-stack:
3 + AWS::S3::Bucket MyS3Bucket
4Do you wish to continue? (Y/n)
5Deploying template 's3.yml' as stack 's3-bucket-stack' in us-east-1.
6Stack s3-bucket-stack: ROLLBACK_COMPLETE
7Messages:
8 - MyS3Bucket: Resource handler returned message: "my-secure-bucket already exists (Service: S3, Status Code: 0, Request ID: null)" (RequestToken: c4a528c1-2fa0-e75e-efb1-5cfacc2aebd6, HandlerErrorCode: AlreadyExists)
9failed deploying stack 's3-bucket-stack'Since S3 is a global service, the bucket name my-secure-bucket exists in another account or region. I modified the BucketName to be how-wtf-rain-bucket and issued the same command:
1Deleted existing, empty stack.
2CloudFormation will make the following changes:
3Stack s3-bucket-stack:
4 + AWS::S3::Bucket MyS3Bucket
5Do you wish to continue? (Y/n)
6Deploying template 's3.yml' as stack 's3-bucket-stack' in us-east-1.
7Stack s3-bucket-stack: CREATE_COMPLETE
8Successfully deployed s3-bucket-stackA couple of awesome things to note:
- It handled the failure of the stack gracefully and gave me the exact issue
- I deployed again and it took care of everything
- I did this all without touching the AWS console.
I am impressed by this tool already!
Deploying a Lambda function
I want to test the rain pkg command with lambda artifacts. Like the cloudformation package command, we specify a lambda directory or handler and it’ll handle zipping and uploading it!
I created a file named lambda.yml with the following contents:
1AWSTemplateFormatVersion: 2010-09-09
2Description: Create an lambda function
3
4Resources:
5 LambdaExecutionRole:
6 Type: AWS::IAM::Role
7 Properties:
8 AssumeRolePolicyDocument:
9 Version: '2012-10-17'
10 Statement:
11 - Effect: Allow
12 Principal:
13 Service:
14 - lambda.amazonaws.com
15 Action:
16 - sts:AssumeRole
17 ManagedPolicyArns:
18 - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
19 MyLambdaFunction:
20 Type: AWS::Lambda::Function
21 Properties:
22 Code: lambdafunction/
23 FunctionName: MyLovelyFunction
24 Handler: index.handler
25 Role: !GetAtt LambdaExecutionRole.Arn
26 Runtime: python3.12
27
28Outputs:
29 LambdaFunctionArn:
30 Description: The ARN of the Lambda function
31 Value: !GetAtt MyLambdaFunction.ArnThis stack creates two resources:
- A lambda execution role with basic permissions granted by the
AWSLambdaBasicExecutionRolepolicy - A lambda function with the
lambdafunction/contents
And outputs the lambda function arn.
I ran the fmt command with the --verify flag:
1rain fmt lambda.yml --verifyThe --verify command is best saved for CI environments since it outputs a 0 or 1 depending if the formatting is correct.
Output:
1lambda.yml: would reformatI ran it with the --write command so that it fixed them:
1rain fmt lambda.yml --writeLet’s run the pkg command with an output file named out.yml:
1rain pkg lambda.yml --output out.ymlHere is a snapshot of my directory structure after doing the pkg command:
1project
2├── lambda.yml
3├── lambdafunction
4│ └── index.py
5└── out.ymlNow let’s deploy the out.yml template:
1rain deploy out.yml lambda-function-stackOutput:
1CloudFormation will make the following changes:
2Stack lambda-function-stack:
3 + AWS::IAM::Role LambdaExecutionRole
4 + AWS::Lambda::Function MyLambdaFunction
5Do you wish to continue? (Y/n)
6Deploying template 'out.yml' as stack 'lambda-function-stack' in us-east-1.
7Stack lambda-function-stack: CREATE_COMPLETE
8 Outputs:
9 LambdaFunctionArn: arn:aws:lambda:us-east-1:012345678901:function:MyLovelyFunction # The ARN of the Lambda function
10Successfully deployed lambda-function-stackUsing the LambdaFunctionArn output, let’s invoke the function to ensure everything worked correctly:
1aws lambda invoke --function-name MyLovelyFunction response.txtHere is the resulting response.txt:
1"Hello world!"View logs of deployments
Another powerful feature of rain is that it can display sensible logs for stack deployments.
Using log command
Using the lambda function stack from the prior section, we can inspect the logs using the log command:
1rain log lambda-function-stackOutput:
1No interesting log messages to display. To see everything, use the --all flagSince there were no “interesting” log messages to display, let’s see them all!
1rain log lambda-function-stack --allOutput:
1Jan 14 20:08:52 lambda-function-stack/lambda-function-stack (AWS::CloudFormation::Stack) CREATE_COMPLETE
2Jan 14 20:08:50 lambda-function-stack/MyLambdaFunction (AWS::Lambda::Function) CREATE_COMPLETE
3Jan 14 20:08:43 lambda-function-stack/MyLambdaFunction (AWS::Lambda::Function) CREATE_IN_PROGRESS "Resource creation Initiated"
4Jan 14 20:08:42 lambda-function-stack/MyLambdaFunction (AWS::Lambda::Function) CREATE_IN_PROGRESS
5Jan 14 20:08:40 lambda-function-stack/LambdaExecutionRole (AWS::IAM::Role) CREATE_COMPLETE
6Jan 14 20:08:24 lambda-function-stack/LambdaExecutionRole (AWS::IAM::Role) CREATE_IN_PROGRESS "Resource creation Initiated"
7Jan 14 20:08:22 lambda-function-stack/LambdaExecutionRole (AWS::IAM::Role) CREATE_IN_PROGRESS
8Jan 14 20:08:19 lambda-function-stack/lambda-function-stack (AWS::CloudFormation::Stack) CREATE_IN_PROGRESS "User Initiated"
9Jan 14 20:08:12 lambda-function-stack/lambda-function-stack (AWS::CloudFormation::Stack) REVIEW_IN_PROGRESS "User Initiated"Generating Gantt chart for deployment times
Another cool feature is viewing the Gantt chart for the deployment times of different resources:
rain log lambda-function-stack --chart > chart.htmlHere is the resulting html page:

Destroying stacks using Rain
Using the prior sections’ stacks, let’s use the rm command to showcase the stack deletion process:
1rain rm lambda-function-stackOutput:
1Stack lambda-function-stack: CREATE_COMPLETE
2Are you sure you want to delete this stack? (y/N) y
3Successfully deleted stack 'lambda-function-stack'and the S3 bucket stack with the -y so that we don’t have to confirm again:
1rain rm s3-bucket-stack -yOutput:
1Successfully deleted stack 's3-bucket-stack'Other features
There are other features not covered in this guide:
- Stack sets
- Forecasting errors using
rain forecast(experimental) - Using rain’s instrinsic functions & modules (experimental)
As previously stated, if you need to work with raw CloudFormation - give this tool a try!