Pulumi is an open source infrastructure as code tool for creating, deploying, and managing cloud infrastructure. Pulumi works with traditional infrastructures like VMs, networks, and databases, in addition to modern architectures, including containers, Kubernetes clusters, and serverless functions.
It leverages existing programming languages—TypeScript, JavaScript, Python, Go, . NET, Java, and markup languages like YAML—and their native ecosystem to interact with cloud resources through the Pulumi SDK. Pulumi let’s you build cloud applications and infrastructure by combining the safety and reliability of infrastructure as code with the power of familiar programming languages and tools.
In this guide, we will explore how to use golang to create an AWS EC2 instance using Pulumi.
Checkout related content:
- How to use Terraform AWS EC2 user_data – aws_instance
- How to use terraform to Launch an AWS EC2 Instance
Installing golang and pulumi
Before you get started using Pulumi, let’s run through a few quick steps to ensure your environment is set up correctly.
First we will need to install pulumi. If you are on a Linux machine, use this command to install.
curl -fsSL https://get.pulumi.com | sh
If you are on a Mac, you can use homebrew
brew install pulumi/tap/pulumi
Consult the installation page here if you are on a different OS. Confirm the installed version
➜ pulumi version
v3.34.1
Next, install the required language runtime, if you have not already. In my case, I am using golang. Checkout the go download and installation page here.
Check the installed golang version to ensure that it is working as expected
➜ go version
go version go1.18.3 darwin/arm64
Configure Pulumi to access your AWS account
Pulumi requires cloud credentials to manage and provision resources. You must use an IAM user account that has Programmatic access with rights to deploy and manage resources handled through Pulumi.
If you have previously installed and configured the AWS CLI, Pulumi will respect and use your configuration settings.
If you do not have the AWS CLI installed or plan on using Pulumi from within a CI/CD pipeline, retrieve your access key ID and secret access key and then set the AWS_ACCESS_KEY_ID
and AWS_SECRET_ACCESS_KEY
environment variables on your workstation.
export AWS_ACCESS_KEY_ID=<YOUR_ACCESS_KEY_ID>
export AWS_SECRET_ACCESS_KEY=<YOUR_SECRET_ACCESS_KEY>
As an optional step, if you have multiple AWS profiles set up, you can specify a different profile to use with Pulumi through one of the following methods:
- Set
AWS_PROFILE
as an environment variable - After creating your project, run
pulumi config set aws:profile <profilename>
Create a new project
Now that you have set up your environment by installing Pulumi, installing your preferred language runtime, and configuring your AWS credentials, let’s create your first Pulumi program.
mkdir citizixaws && cd citizixaws
pulumi new aws-go
The pulumi new
command creates a new Pulumi project with some basic scaffolding based on the cloud and language specified.
If this is your first time running
pulumi new
or most otherpulumi
commands, you will be prompted to log in to the Pulumi service. The Pulumi CLI works in tandem with the Pulumi service in order to deliver a reliable experience. It is free for individual use, with features available for teams. HittingENTER
at the prompt opens up a web browser allowing you to either sign in or sign up.
After logging in, the CLI will proceed with walking you through creating a new project.
First, you will be asked for a project name and description. Hit ENTER
to accept the default values or specify new values.
Next, you will be asked for the name of a stack. Hit ENTER
to accept the default value of dev
.
Finally, you will be prompted for some configuration values for the stack. For AWS projects, you will be prompted for the AWS region. You can accept the default value or choose another value like us-west-2
.
What are projects and stacks? Pulumi projects and stacks let you organize Pulumi code. Consider a Pulumi project to be analogous to a GitHub repo—a single place for code—and a stack to be an instance of that code with a separate configuration. For instance, Project Foo may have multiple stacks for different development environments (Dev, Test, or Prod), or perhaps for different cloud configurations (geographic region for example). See Organizing Projects and Stacks for some best practices on organizing your Pulumi projects and stacks.
After the command completes, the project and stack will be ready.
Review the New Project
Let’s review some of the generated project files:
Pulumi.yaml
defines the project.Pulumi.dev.yaml
contains configuration values for the stack you just initialized.main.go
is the Pulumi program that defines your stack resources.
Let’s examine main.go
.
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v5/go/aws/s3"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Create an AWS resource (S3 Bucket)
bucket, err := s3.NewBucket(ctx, "my-bucket", nil)
if err != nil {
return err
}
// Export the name of the bucket
ctx.Export("bucketName", bucket.ID())
return nil
})
}
This Pulumi program creates a new S3 bucket and exports the name of the bucket.
ctx.Export("bucketName", bucket.ID())
Adding code to create an EC2 Instance
Since we are creating an ec2 instance, replace the code in main.go
with the following:
package main
import (
"fmt"
"github.com/pulumi/pulumi-aws/sdk/v5/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
instanceDetails := map[string]string{
"ubuntu2204srv": "ami-07b5347738d362307",
}
pulumi.Run(func(ctx *pulumi.Context) error {
for key, val := range instanceDetails {
sg, err := ec2.NewSecurityGroup(ctx, key, &ec2.SecurityGroupArgs{
Description: pulumi.String(fmt.Sprintf("SG for instance %v", key)),
VpcId: pulumi.String("vpc-xxxxxxxx"),
Egress: ec2.SecurityGroupEgressArray{
ec2.SecurityGroupEgressArgs{
Protocol: pulumi.String("-1"),
FromPort: pulumi.Int(0),
ToPort: pulumi.Int(0),
CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
},
},
Ingress: ec2.SecurityGroupIngressArray{
ec2.SecurityGroupIngressArgs{
Protocol: pulumi.String("tcp"),
FromPort: pulumi.Int(0),
ToPort: pulumi.Int(65535),
CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
},
},
})
if err != nil {
return fmt.Errorf("could not create sg for %v: %v", key, err)
}
instance, err := ec2.NewInstance(ctx, key, &ec2.InstanceArgs{
Ami: pulumi.String(val),
InstanceType: pulumi.String("t3.medium"),
Tags: pulumi.StringMap{
"Name": pulumi.String(key),
},
AssociatePublicIpAddress: pulumi.Bool(true),
KeyName: pulumi.String("eutychus@main"),
UserData: pulumi.String(fmt.Sprintf(`
#!/bin/bash -e
sudo apt update
sudo apt -y upgrade
sudo hostnamectl set-hostname %v.citizix.com
sudo yum install -y vim wget curl
`, key)),
SubnetId: pulumi.String("subnet-xxxxxxxx"),
VpcSecurityGroupIds: pulumi.StringArray{sg.ID()},
})
if err != nil {
return fmt.Errorf("could not create instance %v: %v", key, err)
}
ctx.Export(fmt.Sprintf("%v:id", key), instance.ID())
ctx.Export(fmt.Sprintf("%v:publicIP", key), instance.PublicIp)
ctx.Export(fmt.Sprintf("%v:privateIP", key), instance.PrivateIp)
}
return nil
})
}
In the code above, we are defining a map of instances with the name and their ids. We are then looping the instances and for each creating a security group and an instance. Please note that the instance will be created in an existing VPC and a subnet that supports instances with public IPs. Please update accordingly.
Finally, we are exporting the instance details.
Deploy the Stack
Let’s go ahead and deploy your stack:
pulumi up
This command evaluates your program and determines the resource updates to make. First, a preview is shown that outlines the changes that will be made when you run the update:
➜ pulumi up
Previewing update (unstable)
View Live: https://app.pulumi.com/citizix/citizixaws/unstable/previews/ffr6shggs-e273-4570-9e1a-392656603dc4
Type Name Plan
+ pulumi:pulumi:Stack citizixaws-unstable create
+ ├─ aws:ec2:SecurityGroup ubuntu2204srv create
+ └─ aws:ec2:Instance ubuntu2204srv create
Resources:
+ 3 to create
Do you want to perform this update? [Use arrows to move, enter to select, type to filter]
yes
> no
details
Once the preview has finished, you are given three options to choose from. Choosing details
will show you a rich diff of the changes to be made. Choosing yes
will create your new Security group and Ec2 instance in AWS. Choosing no
will return you to the user prompt without performing the update operation.
Do you want to perform this update? yes
Updating (unstable)
View Live: https://app.pulumi.com/citizix/citizixaws/unstable/updates/6
Type Name Status
+ pulumi:pulumi:Stack citizixaws-unstable created
+ ├─ aws:ec2:SecurityGroup ubuntu2204srv created
+ └─ aws:ec2:Instance ubuntu2204srv created
Outputs:
ubuntu2204srv:id : "i-02ff31a067b824bd8"
ubuntu2204srv:privateIP: "10.2.40.220"
ubuntu2204srv:publicIP : "35.160.127.123"
Resources:
+ 3 created
Duration: 34s
Remember the output you defined in the previous step? That stack output can be seen in the Outputs:
section of your update. You can access your outputs from the CLI by running the pulumi stack output [property-name]
command. For example you can print the name of your bucket with the following command:
➜ pulumi stack output ubuntu2204srv:publicIP
35.12.127.11
Running that command will print out the public IP for the instance.
If you are using the Pulumi Service backend, you can follow the “View Live” link displayed in the CLI output. This will open the update in the Pulumi Service, where you can view the output and explore detailed information about your stack such as its activity, resources, and configuration.
Next time you make an update, you can run pulumi up
to preview and apply the changes.
Destroying the stack
Now that you’ve seen how to deploy changes to our program, let’s clean up and tear down the resources that are part of your stack.
To destroy resources, run the following:
$ pulumi destroy
You’ll be prompted to make sure you really want to delete these resources. This can take a minute or two; Pulumi waits until all resources are shut down and deleted before it considers the destroy operation to be complete.
➜ pulumi destroy
Previewing destroy (unstable)
View Live: https://app.pulumi.com/citizix/citizixaws/unstable/previews/ffr6shggs-c456-4ed9-be36-2acf0738705e
Type Name Plan
- pulumi:pulumi:Stack citizixaws-unstable delete
- ├─ aws:ec2:Instance ubuntu2204srv delete
- └─ aws:ec2:SecurityGroup ubuntu2204srv delete
Outputs:
- ubuntu2204srv:id : "i-02ff31a067b824bd8"
- ubuntu2204srv:privateIP: "10.2.40.220"
- ubuntu2204srv:publicIP : "35.12.127.11"
Resources:
- 3 to delete
Do you want to perform this destroy? [Use arrows to move, enter to select, type to filter]
yes
> no
details
To delete the stack itself, run
pulumi stack rm
. Note that this removes the stack entirely from the Pulumi Service, along with all of its update history.
Conclusion
Congratulations! You’ve successfully provisioned some cloud resources using Pulumi. By completing this guide you have successfully:
- Created a Pulumi new project.
- Created a new security group
- Provisioned a new EC2 instance.
- Attached the security group to the instances
- Destroyed the resources you’ve provisioned.