| name | pulumi-python |
| description | Creates Pulumi infrastructure-as-code projects in Python, defines cloud resources (AWS, Azure, GCP), configures ESC environments for secrets management, sets up OIDC authentication for secure deployments, and builds multi-language component resources. Use when creating Pulumi Python projects, writing infrastructure code, configuring cloud providers, managing secrets with Pulumi ESC, setting up OIDC for Pulumi, automating infrastructure deployments with Python, creating reusable Pulumi components in Python, or configuring Python toolchains (pip, poetry, uv) for Pulumi. Also use when the user mentions pyproject.toml with Pulumi, component_provider_host, or Python virtual environments for infrastructure code. |
Pulumi Python Skill
Development Workflow
1. Project Setup
pulumi new python
pulumi new aws-python
pulumi new azure-python
pulumi new gcp-python
Project structure:
my-project/
├── Pulumi.yaml
├── __main__.py
├── requirements.txt # or pyproject.toml
└── venv/ # Virtual environment
2. Pulumi ESC Integration
Use Pulumi ESC for centralized secrets and configuration instead of stack config files.
Link ESC environment to stack:
pulumi env init myorg/myproject-dev
pulumi env edit myorg/myproject-dev
pulumi config env add myorg/myproject-dev
ESC environment definition (YAML):
values:
pulumiConfig:
aws:region: us-west-2
myapp:instanceType: t3.medium
aws:
login:
fn::open::aws-login:
oidc:
roleArn: arn:aws:iam::123456789:role/pulumi-oidc
sessionName: pulumi-deploy
secrets:
fn::open::aws-secrets:
region: us-west-2
login: ${aws.login}
get:
dbPassword:
secretId: prod/database/password
environmentVariables:
AWS_ACCESS_KEY_ID: ${aws.login.accessKeyId}
AWS_SECRET_ACCESS_KEY: ${aws.login.secretAccessKey}
AWS_SESSION_TOKEN: ${aws.login.sessionToken}
Validate ESC environment:
pulumi env open myorg/myproject-dev
pulumi env open myorg/myproject-dev --format json | jq .
3. Python Patterns
Basic resource creation:
import pulumi
import pulumi_aws as aws
config = pulumi.Config()
instance_type = config.require("instanceType")
bucket = aws.s3.Bucket(
"my-bucket",
versioning=aws.s3.BucketVersioningArgs(
enabled=True,
),
server_side_encryption_configuration=aws.s3.BucketServerSideEncryptionConfigurationArgs(
rule=aws.s3.BucketServerSideEncryptionConfigurationRuleArgs(
apply_server_side_encryption_by_default=aws.s3.BucketServerSideEncryptionConfigurationRuleApplyServerSideEncryptionByDefaultArgs(
sse_algorithm="AES256",
),
),
),
tags={
"Environment": pulumi.get_stack(),
"ManagedBy": "Pulumi",
},
)
pulumi.export("bucket_name", bucket.id)
pulumi.export("bucket_arn", bucket.arn)
Using dictionary literals (concise alternative):
import pulumi
import pulumi_aws as aws
bucket = aws.s3.Bucket(
"my-bucket",
versioning={"enabled": True},
server_side_encryption_configuration={
"rule": {
"apply_server_side_encryption_by_default": {
"sse_algorithm": "AES256",
},
},
},
tags={
"Environment": pulumi.get_stack(),
"ManagedBy": "Pulumi",
},
)
Component resources for reusability:
import pulumi
from pulumi_aws import lb
class WebServiceArgs:
def __init__(self, port: pulumi.Input[int], image_uri: pulumi.Input[str]):
self.port = port
self.image_uri = image_uri
class WebService(pulumi.ComponentResource):
url: pulumi.Output[str]
def __init__(self, name: str, args: WebServiceArgs, opts: pulumi.ResourceOptions = None):
super().__init__("custom:app:WebService", name, {}, opts)
load_balancer = lb.LoadBalancer(
f"{name}-lb",
load_balancer_type="application",
opts=pulumi.ResourceOptions(parent=self),
)
self.url = load_balancer.dns_name
self.register_outputs({
"url": self.url,
})
Stack references for cross-stack dependencies:
import pulumi
networking_stack = pulumi.StackReference("myorg/networking/prod")
vpc_id = networking_stack.get_output("vpc_id")
subnet_ids = networking_stack.get_output("private_subnet_ids")
Working with Outputs:
import pulumi
uppercase_name = bucket.id.apply(lambda id: id.upper())
combined = pulumi.Output.all(bucket.id, bucket.arn).apply(
lambda args: f"Bucket {args[0]} has ARN {args[1]}"
)
message = pulumi.Output.concat("Bucket ARN: ", bucket.arn)
is_prod = pulumi.get_stack() == "prod"
if is_prod:
alarm = aws.cloudwatch.MetricAlarm(
"alarm",
)
4. Using ESC with pulumi env run
Run any command with ESC environment variables injected:
pulumi env run myorg/aws-dev -- pulumi up
pulumi env run myorg/test-env -- pytest
pulumi env open myorg/myproject-dev --format shell
5. Multi-Language Components
Create components in Python that can be consumed from any Pulumi language (TypeScript, Go, C#, Java, YAML).
Project structure for multi-language component:
my-component/
├── PulumiPlugin.yaml # Required for multi-language
├── pyproject.toml # or requirements.txt
└── __main__.py # Component + entry point
PulumiPlugin.yaml:
runtime: python
Component with proper Args class (main.py):
from typing import Optional
import pulumi
from pulumi import Input, Output, ResourceOptions
from pulumi.provider.experimental import component_provider_host
import pulumi_aws as aws
class SecureBucketArgs:
"""Args class - use Input types for all properties."""
def __init__(
self,
bucket_name: Input[str],
enable_versioning: Optional[Input[bool]] = None,
tags: Optional[Input[dict]] = None,
):
self.bucket_name = bucket_name
self.enable_versioning = enable_versioning or True
self.tags = tags
class SecureBucket(pulumi.ComponentResource):
"""A secure S3 bucket with encryption and versioning."""
bucket_id: Output[str]
bucket_arn: Output[str]
def __init__(
self,
name: str,
args: SecureBucketArgs,
opts: Optional[ResourceOptions] = None,
):
super().__init__("myorg:storage:SecureBucket", name, {}, opts)
bucket = aws.s3.Bucket(
f"{name}-bucket",
bucket=args.bucket_name,
versioning={"enabled": args.enable_versioning},
server_side_encryption_configuration={
"rule": {
"apply_server_side_encryption_by_default": {
"sse_algorithm": "AES256",
},
},
},
tags=args.tags,
opts=ResourceOptions(parent=self),
)
self.bucket_id = bucket.id
self.bucket_arn = bucket.arn
self.register_outputs({
"bucket_id": self.bucket_id,
"bucket_arn": self.bucket_arn,
})
if __name__ == "__main__":
component_provider_host(
name="python-components",
components=[SecureBucket],
)
Publishing for multi-language consumption:
pulumi package add github.com/myorg/my-component
pulumi package add github.com/myorg/my-component@v1.0.0
pulumi package add /path/to/local/my-component
Multi-language Args requirements:
- Use
pulumi.Input[T] type hints for all properties
- Args class must have
__init__ with typed parameters
- Constructor must have
args parameter with type annotation
- Use
Optional[Input[T]] for optional properties
Important: Use component_provider_host() (from pulumi.provider.experimental) as the entry point for multi-language components. This is the modern API — the older pulumi.provider.main() with a custom Provider subclass is deprecated for component authoring.
6. Deployment Workflow
Validate and deploy with error recovery:
pulumi preview
pulumi up
pulumi stack output
For ESC-based deployments:
pulumi env open myorg/myproject-dev
pulumi env run myorg/myproject-dev -- pulumi preview
pulumi env run myorg/myproject-dev -- pulumi up
pulumi stack output
Configuration & Security
- Use Pulumi ESC for all secrets — never commit secrets to stack config files or Pulumi.yaml
- Enable OIDC authentication instead of static credentials
- Use dynamic secrets with short TTLs when possible
- Apply least-privilege IAM policies to OIDC roles
- Use ComponentResources for reusable infrastructure patterns
- Leverage Python type hints for better IDE support and error detection
Common Commands
pulumi env init <org>/<project>/<env>
pulumi env edit <org>/<env>
pulumi env open <org>/<env>
pulumi env run <org>/<env> -- <command>
pulumi env version tag <org>/<env> <tag>
pulumi new python
pulumi config env add <org>/<env>
pulumi preview
pulumi up
pulumi stack output
pulumi destroy
pip install -r requirements.txt
poetry add pulumi-aws
uv add pulumi-aws
Python-Specific Configuration
Using pip (default):
runtime:
name: python
options:
toolchain: pip
virtualenv: venv
Using poetry:
runtime:
name: python
options:
toolchain: poetry
Using uv:
runtime:
name: python
options:
toolchain: uv
virtualenv: .venv
Enable type checking:
runtime:
name: python
options:
typechecker: mypy
References