How I Did It: Step-by-Step Tutorial
Here’s how I built this site and other AWS resources with Pulumi. Let’s walk through it together!
1. Setting Up Pulumi and AWS CLI on Ubuntu
sudo apt update sudo apt install unzip curl -y curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" unzip awscliv2.zip sudo ./aws/install aws --version curl -fsSL https://get.pulumi.com | sh echo 'export PATH=$PATH:$HOME/.pulumi/bin' >> ~/.bashrc source ~/.bashrc pulumi version
I installed AWS CLI and Pulumi on Ubuntu. The --version
commands confirmed everything worked.
2. Creating an IAM User in AWS
In the AWS console, I set up a user for Pulumi:
- Go to IAM > User Groups > Create Group.
- Name it
PulumiUsers
. - Attach policies:
AmazonEC2FullAccess
,AmazonS3FullAccess
,CloudFrontFullAccess
,IAMFullAccess
. - Create the group.
- Go to Users > Add users.
- Name:
pulumi-user
, access: Programmatic access, group:PulumiUsers
. - Save the
Access Key ID
andSecret Access Key
from the.csv
.

3. Configuring AWS Credentials
aws configure
I entered my Access Key ID
, Secret Access Key
, region (us-east-1
), and output format (json
).

4. Creating an EC2 Instance
mkdir my-ec2 && cd my-ec2 pulumi new vm-aws-python
I used Pulumi’s vm-aws-python
template to spin up a simple EC2 instance. Then I ran:
pulumi up
Confirmed with "yes," and my first IaC resource was live!

5. Building the Static S3 Website
mkdir my-site && cd my-site pulumi new static-website-aws-python
I created a new project for the static site you’re seeing now.
6. Fixing an S3 Error
When I ran pulumi up
, I got this error:
TypeError: BucketV2._internal_init() got an unexpected keyword argument 'website'

The fix? I updated __main__.py
to separate the bucket and website config:
bucket = aws.s3.BucketV2("bucket") aws.s3.BucketWebsiteConfigurationV2( "bucket-website", bucket=bucket.bucket, index_document={"suffix": "index.html"}, error_document={"key": "error.html"} ) aws.s3.BucketPublicAccessBlock("public-access-block", bucket=bucket.bucket, block_public_acls=False) aws.s3.BucketOwnershipControls("ownership-controls", bucket=bucket.bucket, rule={"object_ownership": "ObjectWriter"})
7. Adding CloudFront and Deploying
I edited __main__.py
to include CloudFront (full code below), then deployed:
pulumi up --refresh
The --refresh
synced everything, and I got the URLs for my site!

8. Final Code
Here’s the complete __main__.py
for this site:
import pulumi import pulumi_aws as aws import pulumi_synced_folder as synced_folder config = pulumi.Config() path = config.get("path") or "./www" index_document = config.get("indexDocument") or "index.html" error_document = config.get("errorDocument") or "error.html" bucket = aws.s3.BucketV2("bucket") bucket_website = aws.s3.BucketWebsiteConfigurationV2( "bucket-website", bucket=bucket.bucket, index_document={"suffix": index_document}, error_document={"key": error_document} ) aws.s3.BucketOwnershipControls("ownership-controls", bucket=bucket.bucket, rule={"object_ownership": "ObjectWriter"}) aws.s3.BucketPublicAccessBlock("public-access-block", bucket=bucket.bucket, block_public_acls=False) bucket_folder = synced_folder.S3BucketFolder( "bucket-folder", acl="public-read", bucket_name=bucket.bucket, path=path, opts=pulumi.ResourceOptions(depends_on=[ownership_controls, public_access_block]) ) cdn = aws.cloudfront.Distribution( "cdn", enabled=True, origins=[{ "origin_id": bucket.arn, "domain_name": bucket_website.website_endpoint, "custom_origin_config": {"origin_protocol_policy": "http-only", "http_port": 80, "https_port": 443, "origin_ssl_protocols": ["TLSv1.2"]} }], default_cache_behavior={ "target_origin_id": bucket.arn, "viewer_protocol_policy": "redirect-to-https", "allowed_methods": ["GET", "HEAD", "OPTIONS"], "cached_methods": ["GET", "HEAD", "OPTIONS"], "default_ttl": 600, "max_ttl": 600, "min_ttl": 600, "forwarded_values": {"query_string": True, "cookies": {"forward": "all"}} }, price_class="PriceClass_100", custom_error_responses=[{"error_code": 404, "response_code": 404, "response_page_path": f"/{error_document}"}], restrictions={"geo_restriction": {"restriction_type": "none"}}, viewer_certificate={"cloudfront_default_certificate": True} ) pulumi.export("originURL", pulumi.Output.concat("http://", bucket_website.website_endpoint)) pulumi.export("cdnURL", pulumi.Output.concat("https://", cdn.domain_name))