AWS Developer Tools Blog
Increasing development speed with CDK Watch
The AWS Cloud Development Kit (CDK) CLI introduces a new mode of operation, cdk watch
, and two new flags for cdk deploy
, --hotswap
and --no-rollback
. cdk watch
makes development faster by monitoring your code and assets for changes and automatically performing the optimal form of deployment every time a file change is detected, meaning you no longer have to run cdk deploy
each time you make a change to your CDK application. Where possible, cdk watch
will use the --hotswap
flag, which inspects the changes in your project and determines if those changes can be updated in-place without a full deployment through AWS CloudFormation. For CDK assets like your AWS Lambda handler code, Amazon ECS container images, or AWS Step Functions state machines, the CDK CLI will use AWS service APIs to directly make the changes; otherwise it will fall back to performing a full CloudFormation deployment. The --no-rollback
flag will prevent CloudFormation from rolling back failed changes, saving more iteration time on failed deployments.
To see cdk watch
and the --hotswap
and --no-rollback
flags in action, follow the instructions below. You will be using CDK in TypeScript in this blog post, but watch
works with all CDK-supported languages. First, you will create a blank CDK application, and then you will add a simple containerized application using TypeScript and Express to your CDK application. Next, you will write the CDK stack that will create the infrastructure needed to deploy your application. Finally, you will use cdk watch
to iterate on the application code.
Prerequisites
- An AWS account
- A local CDK installation
Setup
Ensure you have the CDK CLI V2 installed (cdk watch
will also work with V1, but these examples are all written with V2 style imports). If you don’t have it installed, see the instructions in the AWS CDK Developer Guide. To verify your installation works correctly, run the cdk --version
command in a terminal; you should see output similar to:
First, create a new CDK application in TypeScript by running the following commands in your terminal.
Application Code
From the cdk-watch
directory, create a directory and the files you will need to build your Docker image.
Next, you must create the package.json
that will declare our application’s dependencies. Note that the only dependency you need is Express; TypeScript does not need to be declared as a dependency, because it will be compiled to JavaScript before the application is deployed. Create docker-app/package.json
and add the following contents.
{
"name": "simple-webpage",
"version": "1.0.0",
"description": "Demo web app running on Amazon ECS",
"license": "MIT-0",
"dependencies": {
"express": "^4.17.1"
},
"devDependencies": {
"@types/express": "^4.17.13"
}
}
Next, you must create the HTML file that will serve as your webpage. Create docker-app/index.html
and add the following code.
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Simple Webpage </title>
</head>
<body>
<div align="center"
<h2>Hello World</h2>
<hr width="25%">
</div>
</body>
</html>
Now, you will create your Express code that will serve the HTML file you just created to any visitors to the site. Create docker-app/webpage.ts
and add the following code.
import * as express from 'express';
const app = express();
app.get("/", (req, res) => {
res.sendFile(__dirname + "/index.html");
});
app.listen(80, function () {
console.log("server started on port 80");
});
Lastly, you will create the Dockerfile
that will start your application. Create docker-app/Dockerfile
and add the following code.
Infrastructure Code
You will now create the CDK stack that defines the infrastructure that will host your webpage. You will use the ApplicationLoadBalancedFargateService
construct from the aws_ecs_patterns
module to greatly simplify your stack. Modify lib/cdk-watch-stack.ts
so that it looks like the following example.
import {
Stack,
StackProps,
aws_ec2 as ec2,
aws_ecs as ecs,
aws_ecs_patterns as ecs_patterns,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
export class CdkWatchStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const vpc = new ec2.Vpc(this, 'Vpc', {
maxAzs: 2,
natGateways: 1,
});
new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'EcsService', {
vpc,
taskImageOptions: {
image: ecs.ContainerImage.fromAsset('docker-app'),
containerPort: 80,
},
});
}
}
Any command specified in the build key (found in cdk.json
) will be invoked prior to synthesis by any deployment, including those from cdk watch
. Your application has TypeScript that needs to be compiled to JavaScript, so add this code to cdk.json
at the same level as the “app”
key:
This creates an entire serverless Docker application. With these changes, run the following commands.
You should see output similar to example below when the deployment finishes.
✅ CdkWatchStack
Outputs:
CdkWatchStack.EcsServiceLoadBalancerDNS6D595ACE = CdkWa-EcsSe-18QPSCKV5G8XP-1157603428.us-east-2.elb.amazonaws.com
CdkWatchStack.EcsServiceServiceURLE56F060F = http://CdkWa-EcsSe-18QPSCKV5G8XP-1157603428.us-east-2.elb.amazonaws.com
Stack ARN:
arn:aws:cloudformation:us-east-2:131099214097:stack/CdkWatchStack/1b15db20-428a-11ec-b96f-0a2907d0130e
Open the link included in the second line of the Outputs section. You should see a page that says Hello World.
Making an Application Code Change
Now that you’ve deployed your application, you can use cdk watch
to make changes to it. Run cdk watch
in a terminal, which should show the following output.
'watch' is observing directory '' for changes
'watch' is observing the file 'cdk.context.json' for changes
'watch' is observing directory 'bin' for changes
'watch' is observing directory 'docker-app' for changes
'watch' is observing directory 'lib' for changes
'watch' is observing the file 'bin/cdk-watch.ts' for changes
'watch' is observing the file 'lib/cdk-watch-stack.ts' for changes
'watch' is observing the file 'docker-app/Dockerfile' for changes
'watch' is observing the file 'docker-app/index.html' for changes
'watch' is observing the file 'docker-app/package.json' for changes
'watch' is observing the file 'docker-app/webpage.ts' for changes
When making application code changes, cdk watch
can speedup the deployment. To see it, make the following change to index.html.
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title> Simple Webpage </title>
</head>
<body>
<div align="center">
<h2>Hello World</h2>
<hr width="25%">
<p>A paragraph</p>
</div>
</body>
</html>
In the terminal you will see cdk watch
deploy this change.
The warning message lets you know that this change is being hotswapped. This means that this change is made by going directly to the service API that provides the resource(s) being updated, bypassing CloudFormation entirely. This introduces drift between your CloudFormation template and your deployed application code. Because of this drift, hotswapping should never be used in a production environment. Hotswap deployments are faster, but lack the robust safety features of CloudFormation deployments, making hotswap deployments ideal for performing rapid code-compile-test loops in your development environment. If you need to disable hotswapping while running watch
, pass the --no-hotswap
flag to watch
. If you need to remove the drift between CloudFormation and your application entirely, simply perform a full CloudFormation deployment by executing cdk deploy
. If you want to perform a hotswap deployment without running cdk watch
, run cdk deploy --hotswap
.
Once this change has been deployed, refresh the page. You should now see the following update to the Hello World page.
Making an Infrastructure Change
Not all resource changes can be hotswapped. Currently, only Lambda Function code changes, ECS Service container definition changes, and Step Functions state machine definition changes can be hotswapped. If any other changes are made, hotswap deployments will fall back to full CloudFormation deployments. To see this, make the following code change to cdk-watch-stack.ts
.
import {
Stack,
StackProps,
aws_ec2 as ec2,
aws_ecs as ecs,
aws_ecs_patterns as ecs_patterns,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
export class CdkWatchStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// Fargate does not work with default VPCs
const vpc = new ec2.Vpc(this, 'Vpc', {
maxAzs: 2, // ALB requires 2 AZs
natGateways: 2, //changing this property does not trigger a hotswap, and a full deployment occurs instead
});
new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'EcsService', {
vpc,
taskImageOptions: {
image: ecs.ContainerImage.fromAsset('docker-app'),
containerPort: 80,
},
});
}
}
Observe the terminal window. After the assets have finished publishing, you will see the following output.
natGateways
used by the
vpc
, so this is an infrastructure change and is therefore not hotswappable; thus,
watch
will fall back to performing a full CloudFormation deployment.
Disabling Rollback
By default, cdk watch
does not use --no-rollback
. Before disabling rollback, enter the ^C
character (control+c
) in the terminal window running cdk watch
, and then run the cdk deploy
command from your terminal.
The full deployment is performed first to make CloudFormation aware of the changes you made earlier. These changes are considered replacement type changes by CloudFormation, which do not support the --no-rollback
flag, because they require the deletion and creation of one of the resources that make up the ApplicationLoadBalancedFargateService
. Once the deployment finishes, run the following command.
You should see the same output you did when you first ran cdk watch
. Now make the following change to your stack:
import {
Stack,
StackProps,
aws_ec2 as ec2,
aws_ecs as ecs,
aws_ecs_patterns as ecs_patterns,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
export class CdkWatchStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// Fargate does not work with default VPCs
const vpc = new ec2.Vpc(this, 'Vpc', {
maxAzs: 2, // ALB requires 2 AZs
natGateways: 2,
});
new ec2.CfnVPC(this, 'mycfnvpc', {
cidrBlock: '10.0.0/16' //intentionally incorrect code
});
new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'EcsService', {
vpc,
taskImageOptions: {
image: ecs.ContainerImage.fromAsset('docker-app'),
containerPort: 80,
},
});
}
}
Note that this change specifies an invalid cidrBlock
. The full deployment result is expected: this is an infrastructure change, so it is not hotswappable. As cdk watch
is attempting the deployment, you will see the following error message.
Without --no-rollback
, this change would be rolled back by CloudFormation. Now make this cidrBlock
valid by making this change:
import {
Stack,
StackProps,
aws_ec2 as ec2,
aws_ecs as ecs,
aws_ecs_patterns as ecs_patterns,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
export class CdkWatchStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// Fargate does not work with default VPCs
const vpc = new ec2.Vpc(this, 'Vpc', {
maxAzs: 2, // ALB requires 2 AZs
natGateways: 2,
});
new ec2.CfnVPC(this, 'mycfnvpc', {
cidrBlock: '10.0.0.0/16' //corrected code
});
new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'EcsService', {
vpc,
taskImageOptions: {
image: ecs.ContainerImage.fromAsset('docker-app'),
containerPort: 80,
},
});
}
}
cdk watch
will detect this change and automatically successfully deploy it with the following output.
Cleaning up
To delete the stack and application that you just deployed, run the cdk destroy
command in your CDK project’s root directory.
Summary
cdk watch
allows you to make more rapid updates to your development stacks by leveraging hotswapping, where possible, to bypass CloudFormation. Not all resource changes can be hotswapped; if a hotswap deployment cannot be performed, watch
will fall back to a full CloudFormation deployment. Due to the intentional drift introduced by hotswapping, it should never be used in a production environment. If desired, hotswapping can be turned off by passing the --no-hotswap
flag. cdk watch
can be invoked with the --no-rollback
flag to disable rollback of failed updates, but any updates that CloudFormation considers as replacement type updates are not affected by this flag.