Tutorial
Welcome to the Takomo hands-on tutorial! In this guide, you'll learn how to use Takomo to manage and deploy AWS CloudFormation stacks. We'll walk through building a real-world infrastructure setup, covering best practices and demonstrating Takomo's key features.
What Will You Build?
Instead of a simple single-stack example, we'll create a more realistic environment:
- A DynamoDB table.
- A VPC without internet access.
- A Lambda function running inside the VPC.
- A VPC endpoint to DynamoDB, so the Lambda can access the table securely.
We'll set up two environments: dev and prod, both deployed in the eu-west-1 region.
Setting Up AWS Credentials
You'll need access to an AWS account where you can safely experiment.
- Create an IAM user with administrator permissions.
- Generate access keys for this user.
- Configure your credentials in your
~/.aws/credentials file, using the profile name takomo-tutorial:
~/.aws/credentials
1[takomo-tutorial]
2aws_access_key_id = ENTER_YOUR_ACCESS_KEY_ID_HERE
3aws_secret_access_key = ENTER_YOUR_SECRET_ACCESS_KEY_HERE
Initializing Your Takomo Project
Let's create a new directory for your Takomo project and set up the required files.
1mkdir takomo-tutorial
2cd takomo-tutorial
From here on, we'll refer to the takomo-tutorial directory as the project's root directory.
Initialize a new NPM project:
Install Takomo as a development dependency:
Project File Structure
Within your project root, create directories to organize your stack configurations and templates:
1mkdir stacks
2mkdir templates
The stacks directory contains all stack configuration files, while the templates directory holds the CloudFormation templates.
Organizing with Stack Groups
Takomo lets you organize stacks into stack groups - directories under stacks that group stacks by environment, region, or other criteria. Stack groups can be nested to create a hierarchical structure, and you can provide shared configuration within each group.
Let's group our stacks by environment and then by region:
1mkdir -p stacks/dev/eu-west-1
2mkdir -p stacks/prod/eu-west-1
Your directory tree should now look like this:
1.
2├─ stacks
3│ ├─ dev
4│ │ └─ eu-west-1
5│ └─ prod
6│ └─ eu-west-1
7├─ templates
8└─ package.json
Creating the DynamoDB Stack
Let's start by defining a CloudFormation template for the DynamoDB table.
Create the template file:
1touch templates/dynamodb.yml
Add the following content:
templates/dynamodb.yml
1Parameters:
2 Environment:
3 Type: String
4 Description: Application environment
5 AllowedValues:
6 - dev
7 - prod
8
9Resources:
10 Table:
11 Type: AWS::DynamoDB::Table
12 Properties:
13 TableName: !Sub my-table-${Environment}
14 BillingMode: PAY_PER_REQUEST
15 AttributeDefinitions:
16 - AttributeName: id
17 AttributeType: S
18 KeySchema:
19 - AttributeName: id
20 KeyType: HASH
21
22Outputs:
23 TableName:
24 Value: !Ref Table
25 TableArn:
26 Value: !GetAtt Table.Arn
This template defines a single DynamoDB table, with the environment name as a suffix.
Now, add the stack configuration for the dev environment:
1touch stacks/dev/eu-west-1/dynamodb.yml
stacks/dev/eu-west-1/dynamodb.yml
1regions: eu-west-1
2template: dynamodb.yml
3parameters:
4 Environment: dev
Explanation:
regions: Tells Takomo where to deploy the stack. (You can specify one or multiple regions.)
template: Path to the template file (relative to templates directory).
parameters: Values for the template parameters.
Your structure should now look like:
1.
2├─ stacks
3│ ├─ dev
4│ │ └─ eu-west-1
5│ │ └─ dynamodb.yml
6│ └─ prod
7│ └─ eu-west-1
8├─ templates
9│ └─ dynamodb.yml
10└─ package.json
Deploying Your First Stack
Let's deploy the DynamoDB stack!
Make sure you're in the project's root directory and run:
1npx tkm stacks deploy --profile takomo-tutorial
You'll see a deployment plan. The stack's path (e.g., /dev/eu-west-1/dynamodb.yml/eu-west-1) uniquely identifies it. Takomo will generate a stack name (e.g., dev-eu-west-1-dynamodb) unless you specify one.
Follow the prompts:
- Choose "continue, but let me review changes to each stack".
- Review the stack-specific plan, then choose "continue to deploy the stack, then let me review the remaining stacks".
Deployment should only take a few seconds. You'll see a summary at the end.
Adding the VPC Stack
Next, let's add a VPC stack.
Create the template:
templates/vpc.yml
1Parameters:
2 Environment:
3 Type: String
4 Description: Application environment
5 AllowedValues:
6 - dev
7 - prod
8 VpcCidr:
9 Type: String
10 Description: VPC CIDR block
11
12Resources:
13 Vpc:
14 Type: AWS::EC2::VPC
15 Properties:
16 CidrBlock: !Ref VpcCidr
17 Subnet:
18 Type: AWS::EC2::Subnet
19 Properties:
20 CidrBlock: !Ref VpcCidr
21 VpcId: !Ref Vpc
22 RouteTable:
23 Type: AWS::EC2::RouteTable
24 Properties:
25 VpcId: !Ref Vpc
26 RouteTableAssociation:
27 Type: AWS::EC2::SubnetRouteTableAssociation
28 Properties:
29 SubnetId: !Ref Subnet
30 RouteTableId: !Ref RouteTable
31
32Outputs:
33 VpcId:
34 Value: !Ref Vpc
35 RouteTableIds:
36 Value: !Ref RouteTable
37 SubnetIds:
38 Value: !Ref Subnet
Now, add the stack configuration:
1touch stacks/dev/eu-west-1/vpc.yml
stacks/dev/eu-west-1/vpc.yml
1regions: eu-west-1
2template: vpc.yml
3parameters:
4 Environment: dev
5 VpcCidr: 10.0.0.0/26
Your files should now look like:
1.
2├─ stacks
3│ ├─ dev
4│ │ └─ eu-west-1
5│ │ ├─ dynamodb.yml
6│ │ └─ vpc.yml
7│ └─ prod
8│ └─ eu-west-1
9├─ templates
10│ ├─ dynamodb.yml
11│ └─ vpc.yml
12└─ package.json
Listing Stacks
To see which stacks are configured and their status, run:
1npx tkm stacks list --profile takomo-tutorial
You should see both the DynamoDB stack (deployed) and the VPC stack (pending deployment).
Deploying the VPC Stack
Run the deploy command again to deploy both stacks:
1npx tkm stacks deploy --profile takomo-tutorial
This time, the plan will show both stacks. The DynamoDB stack will be updated (if needed), and the VPC stack will be created.
Follow the prompts to review and continue the deployment.
Using Shared Configuration with Stack Groups
Notice that both stacks share some configuration, like the environment and region. Let's refactor to use stack groups for shared settings.
TIP
Stack groups allow you to define configuration once and inherit it in all child stacks.
Step 1: Add Shared Configuration for /dev
1touch stacks/dev/config.yml
stacks/dev/config.yml
1data:
2 environment: dev
Step 2: Add Shared Configuration for /dev/eu-west-1
1touch stacks/dev/eu-west-1/config.yml
stacks/dev/eu-west-1/config.yml
Step 3: Update Stack Configurations to Use Shared Values
Edit stacks/dev/eu-west-1/dynamodb.yml:
stacks/dev/eu-west-1/dynamodb.yml
1template: dynamodb.yml
2parameters:
3 Environment: {{ stackGroup.data.environment }}
Edit stacks/dev/eu-west-1/vpc.yml:
stacks/dev/eu-west-1/vpc.yml
1template: vpc.yml
2parameters:
3 Environment: {{ stackGroup.data.environment }}
4 VpcCidr: 10.0.0.0/26
Now, both stacks inherit the region and environment from their stack groups.
Your file structure should look like:
1.
2├─ stacks
3│ ├─ dev
4│ │ ├─ config.yml
5│ │ └─ eu-west-1
6│ │ ├─ config.yml
7│ │ ├─ dynamodb.yml
8│ │ └─ vpc.yml
9│ └─ prod
10│ └─ eu-west-1
11├─ templates
12│ ├─ dynamodb.yml
13│ └─ vpc.yml
14└─ package.json
Redeploy to verify that nothing changes (no updates needed):
1npx tkm stacks deploy --profile takomo-tutorial
Adding a VPC Endpoints Stack
To allow your Lambda function to access DynamoDB privately, add a VPC endpoint stack.
Create the template:
1touch templates/vpc-endpoints.yml
templates/vpc-endpoints.yml
1Parameters:
2 Environment:
3 Type: String
4 Description: Application environment
5 AllowedValues:
6 - dev
7 - prod
8 VpcId:
9 Type: AWS::EC2::VPC::Id
10 Description: Id of the VPC where the endpoints should be created
11 RouteTableIds:
12 Type: CommaDelimitedList
13 Description: Ids of the route tables where the endpoints should be attached
14
15Resources:
16 DynamoDbVpcEndpoint:
17 Type: AWS::EC2::VPCEndpoint
18 Properties:
19 RouteTableIds: !Ref RouteTableIds
20 ServiceName: !Sub com.amazonaws.${AWS::Region}.dynamodb
21 VpcEndpointType: Gateway
22 VpcId: !Ref VpcId
Now, add the stack configuration:
1touch stacks/dev/eu-west-1/vpc-endpoints.yml
stacks/dev/eu-west-1/vpc-endpoints.yml
1template: vpc-endpoints.yml
2parameters:
3 Environment: {{ stackGroup.data.environment }}
4 VpcId:
5 resolver: stack-output
6 stack: vpc.yml
7 output: VpcId
8 RouteTableIds:
9 resolver: stack-output
10 stack: vpc.yml
11 output: RouteTableIds
Parameter Resolvers Explained:
Here, instead of static values, we're using parameter resolvers. The stack-output resolver fetches output values from another stack (in this case, the VPC stack) at deployment time.
Your structure should now look like:
1.
2├─ stacks
3│ ├─ dev
4│ │ ├─ config.yml
5│ │ └─ eu-west-1
6│ │ ├─ config.yml
7│ │ ├─ dynamodb.yml
8│ │ ├─ vpc.yml
9│ │ └─ vpc-endpoints.yml
10│ └─ prod
11│ └─ eu-west-1
12├─ templates
13│ ├─ dynamodb.yml
14│ ├─ vpc.yml
15│ └─ vpc-endpoints.yml
16└─ package.json
Deploying a Single Stack and Its Dependencies
Let's deploy only the VPC endpoints stack (and any dependencies Takomo detects):
1npx tkm stacks deploy /dev/eu-west-1/vpc-endpoints.yml --profile takomo-tutorial
TIP
Takomo automatically detects dependencies between stacks (e.g., via parameter resolvers) and deploys them in the correct order.
Adding the Lambda Function Stack
We're almost done! Now, let's add a Lambda function that will access the DynamoDB table through the VPC endpoint.
First, create a directory to hold code partials and add the Lambda function code:
1mkdir partials
2touch partials/lambda.js
partials/lambda.js
1const AWS = require("aws-sdk")
2const dynamo = new AWS.DynamoDB.DocumentClient()
3
4exports.handler = async (event, context) => {
5 console.log("EVENT: \n" + JSON.stringify(event, null, 2))
6 await dynamo.put({
7 TableName: process.env.TABLE_NAME,
8 Item: {
9 id: Date.now().toString()
10 }
11 }).promise()
12
13 const { Count } = await dynamo.scan({ TableName: process.env.TABLE_NAME }).promise()
14 return Count
15}
Now, create the Lambda template:
1touch templates/lambda.yml
templates/lambda.yml
1Parameters:
2 Environment:
3 Type: String
4 Description: Application environment
5 AllowedValues:
6 - dev
7 - prod
8 VpcId:
9 Type: AWS::EC2::VPC::Id
10 Description: Id of the VPC where the endpoints should be created
11 SubnetIds:
12 Type: CommaDelimitedList
13 Description: Ids of the subnets where the function should be created
14 TableName:
15 Type: String
16 Description: Name of the DynamoDB table
17 TableArn:
18 Type: String
19 Description: ARN of the DynamoDB table
20
21Resources:
22 FunctionSecurityGroup:
23 Type: AWS::EC2::SecurityGroup
24 Properties:
25 GroupDescription: !Sub tutorial-function-${Environment}
26 VpcId: !Ref VpcId
27
28 FunctionRole:
29 Type: AWS::IAM::Role
30 Properties:
31 AssumeRolePolicyDocument:
32 Version: 2012-10-17
33 Statement:
34 - Effect: Allow
35 Principal:
36 Service: lambda.amazonaws.com
37 Action: sts:AssumeRole
38 ManagedPolicyArns:
39 - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
40 Policies:
41 - PolicyName: DynamoDB
42 PolicyDocument:
43 Version: 2012-10-17
44 Statement:
45 - Effect: Allow
46 Action:
47 - dynamodb:PutItem
48 - dynamodb:Scan
49 Resource: !Ref TableArn
50
51 Function:
52 Type: AWS::Lambda::Function
53 Properties:
54 FunctionName: !Sub tutorial-function-${Environment}
55 Handler: index.handler
56 MemorySize: 128
57 Role: !GetAtt FunctionRole.Arn
58 Runtime: nodejs12.x
59 Timeout: 10
60 Environment:
61 Variables:
62 TABLE_NAME: !Ref TableName
63 VpcConfig:
64 SecurityGroupIds:
65 - !Ref FunctionSecurityGroup
66 SubnetIds: !Ref SubnetIds
67 Code:
68 ZipFile: |
69 {{> lambda.js }}
Notice how the Lambda code is included in the template file using a partial ({{> lambda.js }}).
Now, add the stack configuration:
1touch stacks/dev/eu-west-1/lambda.yml
stacks/dev/eu-west-1/lambda.yml
1template: lambda.yml
2parameters:
3 Environment: {{ stackGroup.data.environment }}
4 VpcId:
5 resolver: stack-output
6 stack: vpc.yml
7 output: VpcId
8 SubnetIds:
9 resolver: stack-output
10 stack: vpc.yml
11 output: SubnetIds
12 TableName:
13 resolver: stack-output
14 stack: dynamodb.yml
15 output: TableName
16 TableArn:
17 resolver: stack-output
18 stack: dynamodb.yml
19 output: TableArn
Your structure should now be:
1.
2├─ stacks
3│ ├─ dev
4│ │ ├─ config.yml
5│ │ └─ eu-west-1
6│ │ ├─ config.yml
7│ │ ├─ dynamodb.yml
8│ │ ├─ lambda.yml
9│ │ ├─ vpc.yml
10│ │ └─ vpc-endpoints.yml
11│ └─ prod
12│ └─ eu-west-1
13├─ templates
14│ ├─ dynamodb.yml
15│ ├─ vpc.yml
16│ └─ vpc-endpoints.yml
17└─ package.json
Deploy the stacks to create the Lambda function:
1npx tkm stacks deploy --profile takomo-tutorial
Testing the Lambda Function
With everything deployed, let's test your Lambda function!
If you have the AWS CLI installed, run:
1aws lambda invoke \
2 --region eu-west-1 \
3 --function-name tutorial-function-dev \
4 --profile takomo-tutorial \
5 response.txt
Each invocation increments the number of items in the DynamoDB table. Check response.txt for the updated count.
Alternatively, you can invoke the function from the AWS Management Console.
Setting Up the Production Environment
Now, let's replicate our setup for the prod environment.
Step 1: Add Shared Configuration for prod
1touch stacks/prod/config.yml
stacks/prod/config.yml
1data:
2 environment: prod
Step 2: Add Shared Configuration for prod/eu-west-1
1touch stacks/prod/eu-west-1/config.yml
stacks/prod/eu-west-1/config.yml
Step 3: Create Stack Configuration Files
1touch stacks/prod/eu-west-1/dynamodb.yml
2touch stacks/prod/eu-west-1/lambda.yml
3touch stacks/prod/eu-west-1/vpc.yml
4touch stacks/prod/eu-west-1/vpc-endpoints.yml
Add the following content to each file:
stacks/prod/eu-west-1/dynamodb.yml
1template: dynamodb.yml
2parameters:
3 Environment: {{ stackGroup.data.environment }}
stacks/prod/eu-west-1/lambda.yml
1template: lambda.yml
2parameters:
3 Environment: {{ stackGroup.data.environment }}
4 VpcId:
5 resolver: stack-output
6 stack: vpc.yml
7 output: VpcId
8 SubnetIds:
9 resolver: stack-output
10 stack: vpc.yml
11 output: SubnetIds
12 TableName:
13 resolver: stack-output
14 stack: dynamodb.yml
15 output: TableName
16 TableArn:
17 resolver: stack-output
18 stack: dynamodb.yml
19 output: TableArn
stacks/prod/eu-west-1/vpc.yml
1template: vpc.yml
2parameters:
3 Environment: {{ stackGroup.data.environment }}
4 VpcCidr: 10.0.0.64/26
stacks/prod/eu-west-1/vpc-endpoints.yml
1template: vpc-endpoints.yml
2parameters:
3 Environment: {{ stackGroup.data.environment }}
4 VpcId:
5 resolver: stack-output
6 stack: vpc.yml
7 output: VpcId
8 RouteTableIds:
9 resolver: stack-output
10 stack: vpc.yml
11 output: RouteTableIds
Your directory tree should now look like:
1.
2├─ stacks
3│ ├─ dev
4│ │ ├─ config.yml
5│ │ └─ eu-west-1
6│ │ ├─ config.yml
7│ │ ├─ dynamodb.yml
8│ │ ├─ lambda.yml
9│ │ ├─ vpc.yml
10│ │ └─ vpc-endpoints.yml
11│ └─ prod
12│ ├─ config.yml
13│ └─ eu-west-1
14│ ├─ config.yml
15│ ├─ dynamodb.yml
16│ ├─ lambda.yml
17│ ├─ vpc.yml
18│ └─ vpc-endpoints.yml
19├─ templates
20│ ├─ dynamodb.yml
21│ ├─ vpc.yml
22│ └─ vpc-endpoints.yml
23└─ package.json
Listing and Deploying All Stacks
To see all stacks across both environments:
1npx tkm stacks list --profile takomo-tutorial
You should see four new stacks for the prod environment.
Deploy all stacks (using -y to skip plan review and confirm automatically):
1npx tkm stacks deploy --profile takomo-tutorial -y
Cleaning Up
Congratulations! You've completed the Takomo tutorial and deployed real infrastructure to AWS.
To remove all stacks you created, simply run:
1npx tkm stacks undeploy --profile takomo-tutorial
Thank you for following this tutorial. You now have a solid foundation for managing CloudFormation stacks with Takomo!