AWS CDK: Creating our first VPC

Today, we would like to start building our workshop infrastructure, which includes a dedicated VPC for each participant. VPC sounds hard, right? Let’s see how we can implement it in AWS CDK.

In the previous article, I have briefly explained what we are trying to build, but let’s do a quick recap. Our goal is to develop fully automated infrastructure scripts for setting up an environment for a workshop with multiple participants.

You can easily imagine that each of our participants needs a dedicated VPC to host their resources there. It sounds like a nice and sizable piece to start, so let’s do it!


In order to use CDK, we need to install Node.js in version at least 10.3.0 or higher. It affects everybody, even those working in languages other than TypeScript or JavaScript.

Next, we can install and test the tool:

$ npm install -g aws-cdk # Waiting, waaaaaating, waaaaiting... $ cdk --version 1.33.0 (build 763b2ab)

Also, you can find the complete source code of this example in a following repository: This particular example is under the name: part-1-creating-our-first-vpc.

Bootstrapping new project

After that, we can start development. Let’s initialize our project from a template:

$ cdk init app --language=typescript

It buzzed, zapped, beeped, and at the end, created a bunch of files and directories that altogether represent the CDK Application.

How is it organized?

CDK Application is an additional abstraction that allows us to assemble a logical group and ease reusability between multiple templates and constructs.

It would help if you thought of it as an infrastructure package for a part of the business domain (or bounded context). It has a special meaning in two areas – in the context of the infrastructure lifecycle, and it is the root of the infrastructure graph.

In terms of lifecycle, we have a flow defined via diagram posted below. It already provides a sneak peek in a few areas e.g., in terms of deployment:

AWS CDK Application Lifecycle Diagram from the official documentation.
AWS CDK Application Lifecycle Diagram from the official documentation.

Speaking about the root of the infrastructure graph, you can see that in the bin directory:

#!/usr/bin/env node import 'source-map-support/register'; import * as cdk from '@aws-cdk/core'; import { VpcStack } from '../lib/vpc-stack'; const app = new cdk.App(); new VpcStack(app, 'FirstVPC');

The App construct doesn’t require any initialization arguments because it’s the only construct that can be used as a root for the construct tree.

We can now use the App instance as a scope for defining a single instance of your stack.

Creating resources with Stacks

In the previous snippet, we have imported a stack definition named VpcStack. Let’s investigate its definition:

import * as cdk from '@aws-cdk/core'; export class VpcStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); } }

In this example, by extending the cdk.Stack class, we are providing a construct that will be responsible for generating a CloudFormation stack with given resources.

However, we do not have any resources defined there – so the stack will be empty. That’s not helpful, so let’s address that.

The Power of Defaults

If you wrote or saw a CloudFormation template that defines a VPC, even in the simplest possible case, it is a sizeable chunk of YAML. In order to create and configure VPC properly, we need to create a handful of subnetsrouting tablesinternet and NAT gatewayssecurity groups, choose proper CIDR, and connect them altogether.

Additionally, AWS recommendations are clear: you should use at least 2 Availability Zones (ore even better – 3 AZs) to achieve High Availability and fulfill SLA prerequisites. That directly transfers into having two subnets, and of course, not all subnets should be publicly available. Long story short: you need to codify all those requirements in YAML or JSON, and you are on your own.

How we can handle that in CDK? In such case, we are beneficients of high-level abstractions:

import * as cdk from '@aws-cdk/core'; import { Peer, Port, SecurityGroup, Vpc } from '@aws-cdk/aws-ec2'; export class VpcStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const vpc = new Vpc(this, 'VPC', { maxAzs: 3, natGateways: 3 }); const ssh = new SecurityGroup(this, 'SSH-SG', { securityGroupName: 'open-ssh', vpc, allowAllOutbound: true }); ssh.addIngressRule(Peer.anyIpv4(), Port.tcp(22)); ssh.addIngressRule(Peer.anyIpv6(), Port.tcp(22)); } }
Not sure if happy, amused, or sensing a deception here.
Not sure if happy, amused, or sensing a deception here.

If you see this, and you look like a Futurama Fry meme above, you are right. That does a lot under the hood, and I encourage you to read about all possible configuration knobs in the official documentation.

The great thing above this abstraction above is that it already provides a VPC that is compliant with the AWS recommendations and you can parametrize it to an impressive extent.

So far, so good, it looks great for our use case – so let’s base on it and deploy our stack.


Assuming that you have AWS CLI credentials configured in your environment, you can invoke the following command to check if a deployment succeeds:

# If credentials for AWS CLI are configured, invoke: $ cdk deploy

It It will ask for your confirmation, and after a long moment, you should see that stack succeeded. An excellent addition is that during the whole process, you can monitor the events in your command line.

Successful AWS CDK deployment!
Successful AWS CDK deployment!


You have to be aware that there is one pitfall related to installing aws-cdk globally.

If you will experience and error looking similar to this one:

Argument of type 'this' is not assignable to parameter of type 'Construct'.

Just make sure that you have pinned precisely the same versions for all @aws-cdk libraries in your package.json file as the one you have installed globally. It’s essential to skip tilde (~) or a caret (^):

"devDependencies": { "@aws-cdk/assert": "1.33.0", // ... }, "dependencies": { "@aws-cdk/aws-ec2": "1.33.0", "@aws-cdk/core": "1.33.0", // ... }

Remember to nuke not only the node_modules directory but also package-lock.json file too before reinstalling the dependencies.

What’s next?

We have opened the chocolate box of possibilities here. I omitted the test directory, and we will tackle this opportunity in the next article right away. Some of you are not satisfied with my choice of a programming language – and I will address that as well. Last but not least – we have just scratched the surface when it comes to the abstractions available in the CDK.

Today’s post is already a long one, so allow me to stop here and invite you for the next part.

In the meantime, please tell me what do you think about such a VPC definition that I presented above – is it something you would consider helpful for your projects?

By Wojciech Gawroński

Principal Cloud Architect at Pattern Match. Infrastructure as Code, DevOps culture and AWS aficionado. Functional Programming wrangler (Erlang/Elixir).


This site uses Akismet to reduce spam. Learn how your comment data is processed.