Skip to main content

Multiple Deployment Enviroments With Klotho

Overview

In this tutorial, we'll use Klotho to transform an application to be run in multiple different deployment environments.

We will show how to standardize defaults for development and production environments as well as generate multiple instances of your application for their various stages, like development vs production.

Why?

In many cases, one application needs to be deployed to different environments and these environments may need different setups. For example, within a development environment we may need less storage within our databases then we would in production.

Getting started with deployment environments

Deployment environments can be defined through a combination of Klotho config definition and command line arguements during compilation.

Each generated compiled folder has a one to one link to deployment environments. If we want to have differing configuration of stages and environments, we will want to target new output directories on each compilation.

Prerequisites

Repository

Clone our sample apps git repo and install the npm packages for the ts-orm app.

git clone https://github.com/klothoplatform/sample-apps.git
cd sample-apps/ts-orm
npm install

Application Overview

The ts-orm application utilizes the following annotations

/**
* @klotho::execution_unit {
* id = "sequelize-main"
* }
*/

import * as express from "express";
...

/*
* @klotho::expose {
* target = "public"
* id = "sequelizeApp"
* }
*/
app.listen(3000, async () => {
console.log(`App listening locally`);
});

Klotho compile the application

Let start by compiling our TypeScript application into JavaScript.

npx tsc

Then we'll compile the application using Klotho.

klotho . --app myfirstapp --provider aws
██╗  ██╗██╗      ██████╗ ████████╗██╗  ██╗ ██████╗
██║ ██╔╝██║ ██╔═══██╗╚══██╔══╝██║ ██║██╔═══██╗
█████╔╝ ██║ ██║ ██║ ██║ ███████║██║ ██║
██╔═██╗ ██║ ██║ ██║ ██║ ██╔══██║██║ ██║
██║ ██╗███████╗╚██████╔╝ ██║ ██║ ██║╚██████╔╝
╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝

Adding resource input_file_dependencies:
Adding resource exec_unit:sequelize-main
Found 4 route(s) on server 'app'
Adding resource gateway:sequelizeApp
Adding resource persist_orm:sequelizeDB
Adding resource persist_orm:typeormDB
Adding resource topology:sample
Adding resource infra_as_code:Pulumi (AWS)
Pulumi.myfirstapp.yaml: Make sure to run `pulumi config set aws:region YOUR_REGION --cwd 'compiled/' -s 'myfirstapp'` to configure the target AWS region.

Initial Config of the Application

This initial Klotho compilation will generate a Klotho configuration file at ./compiled/klotho.yaml (based on the --outDir in the CLI) and look similar to:

defaults:
execution_unit:
type: lambda
pulumi_params_by_type:
fargate:
cpu: 256
memory: 512
lambda:
memorySize: 512
timeout: 180
expose:
type: apigateway
persist:
kv:
type: dynamodb
fs:
type: s3
secret:
type: s3
orm:
type: rds_postgres
pulumi_params_by_type:
rds_postgres:
allocatedStorage: 20
instanceClass: db.t4g.micro
skipFinalSnapshot: true
redis:
type: elasticache
pulumi_params_by_type:
elasticache:
nodeType: cache.t3.micro
numCacheNodes: 1
execution_units:
sequelize-main:
type: lambda
pulumi_params:
memorySize: 512
timeout: 180
exposed:
sequelizeApp:
type: apigateway
persisted:
sequelizeDB:
type: rds_postgres
pulumi_params:
allocatedStorage: 20
instanceClass: db.t4g.micro
skipFinalSnapshot: true
typeormDB:
type: rds_postgres
pulumi_params:
allocatedStorage: 20
instanceClass: db.t4g.micro
skipFinalSnapshot: true

Defining stage configs

We can copy the default Klotho config into 2 new files within our applications root directory (dev_klotho.yaml and prod_klotho.yaml) to set a config on a stage basis. This will allow us to define our defaults on a per stage basis as needed and define the CLI flags so the compile is easily repeatable. We'll also remove the parts we don't need to override to keep the file easy to read and to track improvements to the defaults.

For our development environments, we will define our configuration dev_klotho.yaml as:

app: myfirstapp-dev # Updated app name to use -dev suffix to keep its resources in a different namespace
provider: aws
path: dist
out_dir: dev_compiled # Change the out_dir so different environments compile to different directories
defaults:
orm:
type: rds_postgres
pulumi_params_by_type:
rds_postgres:
allocatedStorage: 20 # Leave the storage of the instance at a minimum for development purposes
instanceClass: db.t4g.micro # Make sure this uses a micro instance, in case the default changes
skipFinalSnapshot: true # We do not need to store data on instance deletion within our development environment

Within production, we will define our configuration to have more storage and bigger instances for our relational databases, thus defining the defaults in prod_klotho.yaml as:

app: myfirstapp-prod # Updated app name to use -prod suffix to keep its resources in a different namespace
provider: aws
path: dist
out_dir: prod_compiled # Change the out_dir so different environments compile to different directories
defaults:
orm:
type: rds_postgres
pulumi_params_by_type:
rds_postgres:
allocatedStorage: 200 # Increase the storage of the instance for production load
instanceClass: db.t4g.large # Use a large instance for production load
skipFinalSnapshot: false # Ensure we will have a final snapshot of data on instance deletion

Generating stage and environment level applications

At this point in the tutorial, we understand the defaults we would like in our development and production environments. Now we want to generate the application for those environments.

Dev

To generate our development application, we will recompile using the dev config. We will also direct the compilation to go to a dev compiled directory to keep it separate from other compilations of the application.

klotho --config dev_klotho.yaml
██╗  ██╗██╗      ██████╗ ████████╗██╗  ██╗ ██████╗
██║ ██╔╝██║ ██╔═══██╗╚══██╔══╝██║ ██║██╔═══██╗
█████╔╝ ██║ ██║ ██║ ██║ ███████║██║ ██║
██╔═██╗ ██║ ██║ ██║ ██║ ██╔══██║██║ ██║
██║ ██╗███████╗╚██████╔╝ ██║ ██║ ██║╚██████╔╝
╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝

Adding resource input_file_dependencies:
Adding resource exec_unit:sequelize-main
Found 4 route(s) on server 'app'
Adding resource gateway:sequelizeApp
Adding resource persist_orm:sequelizeDB
Adding resource persist_orm:typeormDB
Adding resource topology:sample
Adding resource infra_as_code:Pulumi (AWS)

If you look at the klotho.yaml generated within the dev_compiled directory, you will see the following configuration for our applications persist resources:

persisted:
sequelizeDB:
type: rds_postgres
pulumi_params:
allocatedStorage: 20
instanceClass: db.t4g.micro
skipFinalSnapshot: true
typeormDB:
type: rds_postgres
pulumi_params:
allocatedStorage: 20
instanceClass: db.t4g.micro
skipFinalSnapshot: true

Prod

To generate our production application, we will recompile using the prod config. We will also direct the compilation to go to a prod compiled directory to keep it separate from other compilations of the application.

klotho --config prod_klotho.yaml
██╗  ██╗██╗      ██████╗ ████████╗██╗  ██╗ ██████╗
██║ ██╔╝██║ ██╔═══██╗╚══██╔══╝██║ ██║██╔═══██╗
█████╔╝ ██║ ██║ ██║ ██║ ███████║██║ ██║
██╔═██╗ ██║ ██║ ██║ ██║ ██╔══██║██║ ██║
██║ ██╗███████╗╚██████╔╝ ██║ ██║ ██║╚██████╔╝
╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝

Adding resource input_file_dependencies:
Adding resource exec_unit:sequelize-main
Found 4 route(s) on server 'app'
Adding resource gateway:sequelizeApp
Adding resource persist_orm:sequelizeDB
Adding resource persist_orm:typeormDB
Adding resource topology:sample
Adding resource infra_as_code:Pulumi (AWS)

If you look at the klotho.yaml generated within the prod_compiled directory, you will see the following configuration for our applications persist resources:

persisted:
sequelizeDB:
type: rds_postgres
pulumi_params:
allocatedStorage: 200
instanceClass: db.t4g.large
skipFinalSnapshot: false
typeormDB:
type: rds_postgres
pulumi_params:
allocatedStorage: 200
instanceClass: db.t4g.large
skipFinalSnapshot: false

If you need to deploy your production application to more than one environment, you can follow the same steps as above, but break it out into multple compiled directories. The nyou will have granular control of each production environments infrastructure, while using the same defaults throughout production.

Deploying the updated application

After following the steps in the Deploying tutorial you know have your dev and prod environments up and running!