How to Deploy to AWS with CDK
Learn to build robust apps using infrastructure-as-code in the cloud.
In this tutorial, we will cover the basics of using AWS CDK so that you can deploy your own applications using Infrastructure-as-Code on AWS.
AWS CDK or Cloud Development Kit is AWS’s framework for defining cloud infrastructure in code and provisioning it through AWS Cloud Formation. The AWS CDK supports TypeScript, JavaScript, Python, Java, C#/.Net, and Go. Using AWS CDK to deploy cloud infrastructure has the following benefits over building AWS applications manually:
- Predictably and repeatedly perform infrastructure deployments with rollbacks on error.
- Easily share infrastructure design defined in CDK
- Add unit tests and source control to make infrastructure more robust
- Take advantage of programming language features such as loops and conditionals to dynamically deploy infrastructure as needed
Prerequisites
Before getting started, here are a few steps you must complete.
Create an AWS Account
If you do not have an AWS account already or would like a separate account for CDK deployments, you need to create a free account here.
Install AWS CLI
The AWS CLI is used to configure authorization between your local system and your AWS account. AWS CLI installation instructions here.
Confirm installation with aws --version
Install Node and TypeScript
Regardless of what language you use, Node will be required for CDK to run. Node installation instructions here.
Confirm installation with npm --version
For this tutorial we will be using TypeScript. You can install TypeScript after installing Node by running the command below.
npm install -g typescript
Install CDK Package
Now you need to install the CDK package and toolkit. This can be done by running the commands below.
npm install aws-cdk-lib
npm install -g aws-cdk
Confirm installation with cdk --version
Note: On Windows, you may need to add the npm path to you environment variables and change the execution policy to run the cdk command. If you are having trouble at this step see this and this tutorial.
Project Overview
In this tutorial we will be making a simple AWS application where we create a Lambda that writes a dummy file to an S3 bucket. First, we will set up a new IAM User and authorize them to deploy infrastructure on your AWS account using Cloud Formation. Then we will define with AWS CDK an S3 bucket, a Python Lambda function, and an IAM Role for the Lambda to write to the S3 bucket. Once these constructs are defined we will deploy them via the CDK. Finally, we will end by cleaning up the resources created with CDK.
Set Up IAM User
It is possible to deploy to your AWS account using the root user, but this is not best practice. It is recommended to create a new user for CDK deployments and grant them minimal permissions. In this tutorial we will create a new user with Admin permissions for simplicity.
Create an IAM User
In your AWS account create a new IAM user with permissions to deploy infrastructure with CDK.
- Go to IAM
- Go to Users and Create User
- In Step 1 enter a username for your user
Create Policy for CDK
Create a new policy for your user to deploy infrastructure using CDK.
4. In Step 2 click on attach policies directly
5. Click on create policy
6. In the JSON tab copy the following:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Resource": [
"arn:aws:iam::*:role/cdk-*"
]
},
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
7. Proceed by selecting Next:Tags > Review
8. Name the policy
9. Select Create Policy > Attach Policy to User > Next
10. Create the user
Create Access Key
Create an access key for your IAM User so that you can configure authorization with the AWS CLI.
11. Go to the newly created user on IAM Users
12. Go to the Security Credentials tab
13. Scroll down and select Create Access Key
14. For usage select CLI and check the box saying that you understand the recommendation
15. Create the access key and download it as a csv file
Configure AWS authorization
Authorize the credentials of the IAM User created with the AWS CLI.
16. In a terminal run this command: aws configure
You will be prompted to input your IAM User credentials, region name and output format.
AWS Access Key ID [None]: <your access key>
AWS Secret Access Key [None]: <your secret key>
Default region name [None]: us-west-1
Default output format [None]: json
Bootstrap your Account
Before making any deployments you must bootstrap your account.
17. Bootstrap your account by running the following command with your information: cdk bootstrap aws://<your account id>/<your region>
Initialize your app
Now we can get started with creating the CDK application. Firstly, we will need to initialize a new app which will create files necessary for CDK deployments.
In a new directory where you would like your CDK application code to be stored, run this command: cdk init app --language typescript
The files created from the above command should be similar to the image below.
bin/cdk_demo.ts is what is called to create the CDK application. In this file, any stacks that you created are imported and initialized.
lib/cdk_demo-stack.ts is a sample stack. Here you define constructs such as S3 buckets or Lambdas.
cdk.json has meta data needed for app creation including pointing to the file that creates the CDK app. Here the file that creates the app is default set to bin/cdk_demo.ts.
Add Infrastructure
With the CDK application initialized, we can add our infrastructure to the lib/cdk_demo-stack.ts file. We will create an S3 bucket, a Lambda Function, and Lambda role for accessing the bucket.
Full Stack Code
Below is all the CDK code for our application in lib/cdk_demo-stack.ts for reference. I will break down each line in the following sections.
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Bucket } from 'aws-cdk-lib/aws-s3';
import {
Function,
Runtime,
Code
} from 'aws-cdk-lib/aws-lambda';
import {
Role,
ServicePrincipal,
PolicyStatement
} from 'aws-cdk-lib/aws-iam';
import * as path from 'path';
export class CdkDemoStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// S3 Bucket
const demoBucket = new Bucket(this, 'demo-bucket-id', {
bucketName: 'unique-bucket-name',
// allow cdk to destroy bucket
removalPolicy: cdk.RemovalPolicy.DESTROY
// Add additional bucket configurations here
});
// Lambda Function Code
const lambdaCode = Code.fromAsset(path.join(__dirname, 'lambda_code'));
// Policy for allowing access to the S3 bucket
const s3AccessPolicy = new PolicyStatement({
actions: ['s3:*'],
resources: [
`${demoBucket.bucketArn}*`,
`${demoBucket.bucketArn}/*`
]
});
// Lambda role for accessing S3
const lambdaRole = new Role(this, 'lambda-role-id', {
assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
});
// Add policy to lambda role
lambdaRole.addToPolicy(s3AccessPolicy);
// Lambda
const demoLambda = new Function(this, 'lambda-id', {
runtime: Runtime.PYTHON_3_9,
handler: 'demo_lambda.lambda_handler',
code: lambdaCode,
role: lambdaRole,
environment: {
// Passes bucket name as environment variable
BUCKET_NAME: demoBucket.bucketName
}
});
}
}
S3 Bucket
The below code defines a new S3 bucket. The name provided in the bucketName
field must be unique. the removalPolicy
is set to allow CDK to destroy this bucket during cleanup. The default will prevent CDK from destroying the bucket to ensure data is not accidentally deleted.
//IMPORTS
//---------------------------
import * as cdk from 'aws-cdk-lib';
import { Bucket } from 'aws-cdk-lib/aws-s3';
// STACK CONSTRUCTOR METHOD
//---------------------------
// S3 Bucket
const demoBucket = new Bucket(this, 'demo-bucket-id', {
bucketName: 'unique-bucket-name',
// allow cdk to destroy bucket
removalPolicy: cdk.RemovalPolicy.DESTROY
// Add additional bucket configurations here
});
Lambda Code
Create a new folder within lib named lambda_code. Within this new folder, create a Python file named demo_lambda.py. Copy the below code into the file. This code creates a dummy file and writes it to an S3 bucket.
import boto3
import os
from datetime import datetime
import logging
def lambda_handler(event, context):
write_dummy_file()
def write_dummy_file():
s3 = boto3.resource('s3')
bucket_name = os.environ['BUCKET_NAME']
content_string = "blah blah"
encoded_string = content_string.encode('utf-8')
file_name = f"dummy_file_{datetime.now().strftime('%Y%m%d')}.txt"
s3_file_path = f"test/{file_name}"
log_success = f"{file_name} was uploaded to {bucket_name}."
log_fail = f"Error occured while attempting to write to {bucket_name}"
try:
s3.Bucket(bucket_name).\
put_object(Key=s3_file_path, Body=encoded_string)
logging.info(msg=log_success)
except Exception as e:
logging.error(msg=f"{log_fail}:\n{e}")
Lambda Code CDK
Back in the Stack, you can point to the Python file you just created using the snippet below.
//IMPORTS
//---------------------------
import { Code } from 'aws-cdk-lib/aws-lambda';
import * as path from 'path';
// STACK CONSTRUCTOR METHOD
//---------------------------
// Lambda Function Code
const lambdaCode = Code.fromAsset(path.join(__dirname, 'lambda_code'));
S3 Access Policy
We must create a new policy for the Lambda to be able to write to S3. The policy required is defined below.
//IMPORTS
//---------------------------
import { PolicyStatement } from 'aws-cdk-lib/aws-iam';
// STACK CONSTRUCTOR METHOD
//---------------------------
// Policy for allowing access to the S3 bucket
const s3AccessPolicy = new PolicyStatement({
actions: ['s3:*'],
resources: [
`${demoBucket.bucketArn}*`,
`${demoBucket.bucketArn}/*`
]
});
Lambda Role
Define a new role the the Lambda can assume and attach the policy we just created to it.
//IMPORTS
//---------------------------
import {
Role,
ServicePrincipal,
PolicyStatement
} from 'aws-cdk-lib/aws-iam';
// STACK CONSTRUCTOR METHOD
//---------------------------
// Lambda role for accessing S3
const lambdaRole = new Role(this, 'lambda-role-id', {
assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
});
// Add policy to lambda role
lambdaRole.addToPolicy(s3AccessPolicy);
Lambda
Finally, we can define the Lambda by using the Code, Role and Bucket defined above. Make sure the handler
is correctly mapped to your Python file and handler function.
//IMPORTS
//---------------------------
import {
Function,
Runtime,
Code
} from 'aws-cdk-lib/aws-lambda';
// STACK CONSTRUCTOR METHOD
//---------------------------
// Lambda
const demoLambda = new Function(this, 'lambda-id', {
runtime: Runtime.PYTHON_3_9,
handler: 'demo_lambda.lambda_handler',
code: lambdaCode,
role: lambdaRole,
environment: {
// Passes bucket name as environment variable
BUCKET_NAME: demoBucket.bucketName
}
});
Deploy to AWS
We can now deploy the infrastructure defined with CDK to our AWS account. To do this, only 2 steps are required.
- Run the command
cdk synth
to convert your CDK code to Cloud Formation template - Run the command
cdk deploy
to deploy your infrastructure to your AWS account
After inputting yes when prompted in the command line you will see output similar to that of below.
After the deployment completes, if you go to your account, you will see your Lambda and S3 deployed. You can execute your lambda and see a new file created in your S3 bucket
Clean Up
Once you are done using the resources deployed with CDK, we can clean up everything simply by running the following command: cdk destroy
Summary and Final Thoughts
In this tutorial you learned how to install, configure, and use AWS CDK to deploy infrastructure as code to AWS. You created a new CDK stack that defined an S3 bucket and a Lambda that writes sample files to that bucket. You should now have the knowledge necessary to start converting your existing AWS apps to CDK or even starting new CDK apps from scratch. As you explore and develop your CDK applications, I recommend you first test by creating resources manually and confirming what works and what does not.