Getting Started with Pulumi


Table of Contents
Jump to a section
Introduction
Pulumi is a modern Infrastructure as Code platform. Like other tools like Terraform, you can define your whole infrastructure in code.
The main difference is that Pulumi is not limited to a specific cloud provider. You can use it to deploy your infrastructure to multiple cloud providers, including AWS, Azure, Google Cloud, and Kubernetes.
Additionally, Pulumi is not limited to just cloud resources. It's also possible to deploy your infrastructure to other platforms, like Docker, Kubernetes, and even your own data center.
Furthermore, Pulumi allows you to use your favorite programming language. This means, you can use Python, TypeScript, Go, JavaScript, and more.
In this article, we will look at how to get started with Pulumi. At end, you'll know everything you're set up to start building your own infrastructure.
Key Concepts
Like Terraform, Pulumi is also a declarative tool. This means, we'll describe our desired state of our infrastructure. Pulumi will then take care of creating this state through actions.
On the host, we'll write our code in a programming language of our choice. Pulumi will then compute this into a plan, which is a list of actions that need to be taken to reach the desired state.
This means it will calculate which resources need to be
- created,
- updated,
- or deleted.
The state is later stored in a stack. This stack can live in a remote or local directory.
For serious projects, it's a requirement to use a remote stack, as otherwise, collaboration is not possible.
Projects
For Pulumi, every folder that contains a Pulumi.yaml
file is considered a project.
If there's no Pulumi project yet, you can create one by running pulumi new
.
As we've learned, the supported runtimes are nodejs
, python
, dotnet
, go
, java
and yaml
.
The project file itself is a simple, short YAML file:
name: awsfundamentals
runtime: nodejs
description: A simple Pulumi project
When using NodeJS, by default, Pulumi will use TypeScript. You can change this by setting
options.typescript
tofalse
.
With NodeJS based projects, we'll also need a package.json
file.
This way, we can install dependencies that are needed for our project.
Also worth noticing, everything you refer to on the local filesystem needs to be relative to the project's root.
Stacks
Stacks are a way to manage different environments.
For example, we can have a dev
and a prod
stack.
Each stack can have it's own configuration.
When we create a new stack, Pulumi will also create a Pulumi.$STACK_NAME.yaml
file.
This file can contain any configuration that we want to use for this stack.
config:
awsfundamentals:
propertyOne: valueOne
propertyTwo:
key: value
You can set the configuration properties via the CLI or directly in the YAML file.
pulumi config set -s <stack-name> <property-name> <value>
Inside your infrastructure file, e.g. your index.ts
in the case of a NodeJS project, you can access your configuration properties and your stack name at any time.
import { Config, getStack } from '@pulumi/pulumi';
const config = new Config();
const propertyOne = config.require("propertyOne");
const stackName = getStack();
The config object also has other methods, like requireObject
which allows us to require a configuration object.
If it's not set, it will throw an error.
const config = new Config();
const propertyOne = config.require("propertyOne");
const propertyTwo = config.requireObject<{ key: string }>("propertyTwo");
Resources
Resources are the main building blocks in Pulumi. They actually make up your infrastructure.
In case of AWS, a resource is a single AWS resource, like a VPC, a security group, an EC2 instance, etc.
There are two subclasses of resources:
- CustomResource: a cloud resource managed by a resource provider, e.g. AWS.
- ComponentResource: a resource that is composed of other resources, e.g. a VPC and an EC2 instance. These are the higher building blocks that are used to create more complex infrastructure via composition.
Higher building blocks are also called components. They are very useful when we want to create reusable infrastructure.
In the example above, we're having a component that is composed of resources needed to run a containerized application in Fargate.
Inputs and Outputs
Now to one of the most important concepts in Pulumi: inputs and outputs.
All resources that Pulumi accepts are called inputs.
For example, the aws.lambda.Function
resource accepts inputs like name
, handler
, runtime
, etc.
const myLambda = new aws.lambda.Function('myLambda', {
name: 'my-function',
handler: 'index.handler',
runtime: 'nodejs22.x',
});
Pulumi will later translate this into a plan and map the inputs to the API call's parameters.
Outputs are the other way around. They are the values that are returned by a resource.
For example, the aws.lambda.Function
resource returns an arn
or name
output.
const lambdaArn = myLambda.arn;
const lambdaName = myLambda.name;
The output is calculated when the resource is created. Pulumi can't know it beforehand, as it's a runtime value.
Nevertheless, we can still access it via the arn
output property.
Now, here comes the magic of Pulumi: We can also use this output as an input for other resources.
const cloudWatchLogGroup = new aws.cloudwatch.LogGroup('myLogGroup', {
name: `/aws/lambda/${myLambda.name}`,
retentionInDays: 14,
});
This way, we can create a log group for our lambda function. We don't need to hardcode the name of the log group, as it's dynamically created by Pulumi.
If we'd change the name of the lambda function, the log group would automatically be renamed to match the new lambda function's name.
Secrets
Secrets are a way to manage sensitive information, like passwords, API keys, etc. This way, you don't need to work with plain text secrets in your Pulumi configuration files.
With the CLI, we can easily encrypt secrets and push them to our configuration files.
pulumi config set --secret mySecret mySecretValue
This will encrypt the secret and save it to the configuration file.
By default, Pulumi will use a key that is managed by the Pulumi Cloud. Alternatively, you can also use other key management systems, like AWS KMS, Azure Key Vault, and Google Cloud KMS. This way, the keys are managed by you or your organization.
Inside our code, we can access the secret via the Config
object.
const config = new Config();
const mySecret = config.requireSecret("mySecret");
The great thing here: Pulumi will never print the secret value to the console. Even if we decide to explicitly print the secret value, it will be masked.
console.log(`Password: ${mySecret}`);
$ pulumi up
Password: [secret]
State Management and Backends
When Pulumi creates resource for you, it needs to store metadata so it knows what it created. This metadata is stored in the state file.
This state file should live in a remote backend; else, collaboration will be a pain.
Luckily, Pulumi is very flexible when it comes to state management.
We can choose where to store the state file. Options include but are not limited to:
- AWS S3
- Microsoft Azure Blob Storage
- Google Cloud Storage
Alternatively, you can also use the Pulumi Cloud as a backend.
When you decide to use a remote backend, you always need to login before using the CLI.
pulumi login <backend-url>
# e.g. for Amazon S3
pulumi login s3://my-bucket
# ... or with an Azure Blob Storage
pulumi login azblob://my-container
Hands-On
Now that we know the key concepts, let's bring them to life.
What we'll do:
- Configuring Pulumi so that it's able to deploy to AWS.
- Creating a new project and stack.
- Provisioning a simple S3 bucket via the Pulumi starter template.
- Deploying everything.
- Destroying the stack and cleaning up.
Prerequisites and Installation
Before we start, we need to install Pulumi.
- 🍏 macOS: we can simple install Pulumi via Homebrew.
- 🐧 Linux: we can use curl to run the installation script for the latest version via
curl -fsSL https://get.pulumi.com | sh
. - 🪟 Windows: by using PowerShell, we can use chocolatey to install Pulumi via
choco install pulumi
.
Choosing our Programming Language
As mentioned earlier, Pulumi doesn't come with it's own language. Instead, we can use our favorite programming language.
Supported languages are:
- Python
- TypeScript
- Go
- JavaScript
- C#
- Java
You can also make use of YAML, but I don't recommend it.
In this guide, we'll use TypeScript.
Setting up our AWS Access
We'll now install the AWS CLI.
When using macOS, you can use homebrew via brew install awscli
to get the latest version.
For Linux and Windows, there's a similar process.
Configuration of our Credentials
You need to be authenticated and authorized to access your account via the AWS CLI. This requires you to have an Access Key ID and Secret Access Key.
Let's head over to your user's security credentials settings so we can create a pair of those if you haven't done this already.
Security Best Practice: While using long-term access keys is convenient for this tutorial, it's recommended to use temporary session tokens with Multi-Factor Authentication (MFA) for enhanced security in production environments. Learn more in our guide on AWS Authentication Best Practices.
We'll select the Command Line Interface (CLI). The AWS console may note that it's recommended to make use of the IAM Identity Center to access the AWS CLI.
Make sure to save your Secret Access Key, as you can't display it again.
Now we can jump back into our terminal and run aws configure
which will prompt us for our previously created Access Key and Secret Access Key.
It will also ask for a default region, e.g. us-east-1, and the default output format, e.g. json
.
If you've set up everything properly, you should be able to get a few details of your account and user by running aws sts get-caller-identity
.
{
"UserId": "AROASJEZZYDC2DOJKSDXY:john.doe",
"Account": "012345678901",
"Arn": "arn:aws:sts::012345678901:assumed-role/AWSReservedSSO_AdministratorAccess_bc0e725dc894596b/john.doe"
}
Now we're ready to start building!
Creating a New Project and Stack
We'll store our state in an Amazon S3 bucket. Let's create that bucket first
aws s3 mb s3://pulumi-hands-on-awsf
make_bucket: pulumi-hands-on-awsf
Now, we can already log in to Pulumi.
pulumi login s3://pulumi-hands-on-awsf
Logged in to localhost as john.doe (s3://pulumi-hands-on-awsf)
Now, we can create a new project by using the pulumi new
command and using the aws-typescript
template.
mkdir pulumi-hands-on-awsf
cd pulumi-hands-on-awsf
pulumi new aws-typescript
You'll be asked a lot of questions while going through the process, including:
- The name of the project
- Project description
- Stack name
- Phrase to protect the secrets
- Your desired package manager
- The AWS region to deploy to
Afterward, you'll see a success message like this:
Your new project is ready to go! ✨
To perform an initial deployment, run `pulumi up`
Our project is now ready to go! 🎉
Let's have a look at our index.ts
file.
import * as aws from '@pulumi/aws';
// Create an AWS resource (S3 Bucket)
const bucket = new aws.s3.BucketV2('my-bucket');
// Export the name of the bucket
export const bucketName = bucket.id;
It's a very minimalistic example, but it's enough to get us started.
Now, we can already perform an initial deployment.
pulumi up
Pulumi will now translate our TypeScript code into a plan.
As we can see, Pulumi detected that we want to create a new S3 bucket.
We can now decide whether we want to apply this plan or not (this can also be skipped by using the --yes
flag).
Let's confirm the plan and apply it.
Now, we can head over to our S3 bucket and see that it's been created.
aws s3 ls | grep my-bucket
2025-04-17 08:30:44 my-bucket-8690ee6
Now, we can destroy the stack and clean up!
pulumi destroy
Now, we can head over to our S3 bucket and see that it's been deleted.
aws s3 ls | grep my-bucket
No results should be returned.
That's already it! 🎉
You've learned the key concepts of Pulumi and how to create a new project and stack.
Summary
In this article, we've learned the key concepts of Pulumi. We've also learned how to create a new project and stack.
We've then deployed a simple S3 bucket and cleaned up after ourselves.
Generally, Pulumi is a very powerful and flexible tool to manage infrastructure. Due to the many supported languages, it's also very easy to integrate into your existing workflow. Developers don't need to learn a new language or tool, but they can keep using their favorite language. Even for the infrastructure part.
I hope you've enjoyed this article and you're excited to start building with Pulumi.