This walks through creating an object storage bucket on all three major clouds with Pulumi and Python: an S3 bucket on AWS, a Cloud Storage bucket on Google Cloud, and a blob container on Azure. Same tool, same language, three providers. The examples use current resource names — AWS in particular changed which S3 resource is recommended.
How do you set up a Pulumi project for cloud storage?
A Pulumi program is a normal Python project. Create the project, then add the provider SDK you need. Each cloud has its own package: pulumi-aws, pulumi-gcp, and pulumi-azure-native.
mkdir buckets && cd buckets
pulumi new python --name buckets --yes
# add the provider SDKs you need
pip install pulumi-aws pulumi-gcp pulumi-azure-native
pulumi config set aws:region us-east-1
pulumi config set gcp:project my-gcp-project
pulumi config set azure-native:location eastus
Provider credentials come from the same environment the CLI tools use — the AWS credentials chain, gcloud auth application-default login, and an Azure CLI login or service principal. Pulumi does not manage auth itself.
How do you create an S3 bucket with Pulumi in Python?
Use aws.s3.Bucket. The older aws.s3.BucketV2 is now deprecated in favor of it. In the current model, most settings — versioning, encryption, public access blocking, lifecycle rules — are separate resources that reference the bucket, not inline arguments.
import pulumi_aws as aws
bucket = aws.s3.Bucket('assets',
bucket='my-app-assets-prod',
tags={'Environment': 'prod'})
aws.s3.BucketVersioning('assets-versioning',
bucket=bucket.id,
versioning_configuration={'status': 'Enabled'})
aws.s3.BucketServerSideEncryptionConfiguration('assets-sse',
bucket=bucket.id,
rules=[{
'apply_server_side_encryption_by_default': {'sse_algorithm': 'AES256'},
}])
aws.s3.BucketPublicAccessBlock('assets-pab',
bucket=bucket.id,
block_public_acls=True,
block_public_policy=True,
ignore_public_acls=True,
restrict_public_buckets=True)
⚠ Note: a plain aws.s3.Bucket is not private by default in every account configuration. Add the BucketPublicAccessBlock resource unless you specifically need public objects.
How do you create a Google Cloud Storage bucket with Pulumi?
Use gcp.storage.Bucket. Google’s resource keeps the common settings inline: uniform_bucket_level_access replaces per-object ACLs with IAM, and versioning is a nested block.
import pulumi_gcp as gcp
bucket = gcp.storage.Bucket('assets',
name='my-app-assets-prod',
location='US',
uniform_bucket_level_access=True,
versioning={'enabled': True},
lifecycle_rules=[{
'action': {'type': 'Delete'},
'condition': {'age': 365},
}])
uniform_bucket_level_access=True is the setting you almost always want — it turns off object ACLs and makes IAM the single source of access truth.
How do you create an Azure Blob container with Pulumi?
Azure needs two resources: a storage.StorageAccount, then a storage.BlobContainer inside it. Both live in a resource group.
import pulumi_azure_native as azure_native
account = azure_native.storage.StorageAccount('assets',
account_name='myappassetsprod',
resource_group_name='my-rg',
location='eastus',
kind=azure_native.storage.Kind.STORAGE_V2,
sku={'name': azure_native.storage.SkuName.STANDARD_LRS},
allow_blob_public_access=False,
minimum_tls_version=azure_native.storage.MinimumTlsVersion.TLS1_2)
container = azure_native.storage.BlobContainer('assets-container',
account_name=account.name,
resource_group_name='my-rg',
container_name='assets',
public_access=azure_native.storage.PublicAccess.NONE)
Azure’s blob versioning is a property of the account’s blob service rather than the container, so it is configured separately if you need it.
How do the three providers compare for bucket creation?
| AWS | Google Cloud | Azure | |
|---|---|---|---|
| Pulumi resource | aws.s3.Bucket | gcp.storage.Bucket | storage.StorageAccount + storage.BlobContainer |
| Versioning | Separate BucketVersioning resource | Inline versioning block | Account blob-service property |
| Public access control | Separate BucketPublicAccessBlock | Inline public_access_prevention | allow_blob_public_access + container public_access |
| ACL model | Separate BucketAcl (or block it) | uniform_bucket_level_access | RBAC / shared keys / SAS |
| Lifecycle rules | Separate BucketLifecycleConfiguration | Inline lifecycle_rules | Management policy on the account |
AWS spreads configuration across many small resources; Google keeps it on one; Azure splits along the account/container line. Once the project is set up, running all three is one pulumi up.
pulumi preview # show what will be created
pulumi up # create it
pulumi destroy # tear it down
For turning this into a reusable module and wiring it into CI, see the infrastructure documentation guide and the GitHub Actions workflow guide.
Frequently Asked Questions
Use aws.s3.Bucket. BucketV2 is deprecated in the current AWS provider. In the current model, versioning, encryption, public-access blocking, and lifecycle rules are separate resources that reference the bucket by id.
Yes, on AWS. Versioning is aws.s3.BucketVersioning, encryption is aws.s3.BucketServerSideEncryptionConfiguration, public access is aws.s3.BucketPublicAccessBlock, and lifecycle is aws.s3.BucketLifecycleConfiguration. Google Cloud keeps most of these inline on the bucket.
A setting that disables per-object ACLs so IAM policies are the only way access is granted. Setting uniform_bucket_level_access=True in Pulumi is the recommended default for new buckets.
Yes. Install pulumi-aws, pulumi-gcp, and pulumi-azure-native in the same project, set each provider’s config, and define resources from all three. A single pulumi up creates them together.
Quick Summary
- Use aws.s3.Bucket (not the deprecated BucketV2); AWS bucket settings are separate resources referencing bucket.id.
- gcp.storage.Bucket keeps versioning and uniform_bucket_level_access inline — set the latter to True for new buckets.
- Azure needs a StorageAccount plus a BlobContainer; set allow_blob_public_access=False and minimum_tls_version to TLS1_2.
- One Pulumi project can hold all three providers — install the SDKs, set each provider’s config, run pulumi up once.
- pulumi preview before pulumi up, every time.
Building this into a shared module? Pair it with the GitHub Actions workflow guide to run pulumi preview on every pull request.