AWS Exam GuidesNewsletterBlogContact

👋 Hey! My name is Wojciech Gawroński, but others call me AWS Maniac.

My goal is to safely guide you through the cloudy and foggy space of the AWS portfolio.

Creating our first VPC in AWS CDK

5 min read, last updated on 2020-12-22

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!


First, we need to install and configure AWS CLI. There is plenty of tutorials available over the internet for that, so feel free to do it and return back here.

Second, We need to install Node.js in version at least 10.3.0 or higher to use CDK. 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

You can also find the complete source code of this example in the following repository: github.com/afronski/awsmaniac-aws-cdk. 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 the 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.

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 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 purpose:

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 provide 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, it is a sizeable chunk of YAML even in the simplest possible case. In order to create and configure VPC properly, we need to create a handful of subnets, routing tables, internet and NAT gateways, security groups, choose proper CIDR, and connect them all together.

Additionally, AWS recommendations are clear: you should use at least 2 Availability Zones (or 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 {
} 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',
      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.

If you see this and 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 about this abstraction above is that it already provides a VPC 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 will ask for your confirmation, and after a long moment, you should see that stack succeeded. An excellent addition is that you can monitor the events in your command line during the whole process.

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 the node_modules directory and the 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?

Subscribe to the newsletter and get notifications about new posts.


👋 Hey! My name is Wojciech Gawroński, but some people call me AWS Maniac. I am your trusted guide through the AWS Madness. If you want to learn more about me, you can start here.

Share it:

YouTube, Twitter, LinkedIn, Instagram, Facebook
awsmaniac.com © 2021, built with Gatsby and template from @kjendrzyca