Pulumi is an Infrastructure as Code platform that lets you define cloud resources using general-purpose programming languages such as Python, TypeScript, or Go, rather than a domain-specific language. On RHEL 8, Pulumi pairs naturally with the existing Python ecosystem, allowing you to create and manage AWS resources using familiar constructs like loops, functions, and modules. This tutorial walks through installing the Pulumi CLI, bootstrapping a Python project that provisions an S3 bucket and an EC2 instance, previewing and deploying the stack, and managing multiple stacks for development and production environments.

Prerequisites

  • RHEL 8 server with Python 3.8 or newer and pip
  • AWS CLI configured with credentials (aws configure) or environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
  • A non-root user with sudo privileges
  • Outbound internet access to reach get.pulumi.com and AWS API endpoints

Step 1 — Install the Pulumi CLI

The official Pulumi install script downloads the latest release, installs it to ~/.pulumi/bin, and updates your shell profile so the pulumi command is immediately available.

# Install Pulumi via the official install script
curl -fsSL https://get.pulumi.com | sh

# Add Pulumi to your PATH for the current session
export PATH="$HOME/.pulumi/bin:$PATH"

# Persist it across sessions
echo 'export PATH="$HOME/.pulumi/bin:$PATH"' >> ~/.bashrc

# Verify installation
pulumi version

# Log in using the local filesystem backend (no Pulumi Cloud account needed)
pulumi login --local

Step 2 — Bootstrap a New AWS Python Project

Create a project directory, initialise a Pulumi stack using the AWS Python template, and install the generated Python dependencies.

mkdir ~/pulumi-rhel8-demo && cd ~/pulumi-rhel8-demo

# Create a new Pulumi project using the AWS Python template
pulumi new aws-python --name rhel8-demo --description "RHEL 8 Pulumi demo" --stack dev

# pulumi new creates a virtual environment automatically; activate it
source venv/bin/activate

# Review the generated files
ls -la
# Outputs: Pulumi.yaml  Pulumi.dev.yaml  __main__.py  requirements.txt  venv/

# Install dependencies
pip install -r requirements.txt

Step 3 — Write the Pulumi Python Program

Replace the generated __main__.py with a program that creates an S3 bucket with versioning enabled and an EC2 instance in the default VPC.

cat > __main__.py <<'EOF'
import pulumi
import pulumi_aws as aws

# ── Configuration ────────────────────────────────────────────────────────────
config = pulumi.Config()
instance_type = config.get("instanceType") or "t3.micro"
ami_id = config.get("amiId") or "ami-0c02fb55956c7d316"  # Amazon Linux 2 us-east-1

# ── S3 Bucket with versioning ────────────────────────────────────────────────
bucket = aws.s3.Bucket(
    "app-artifacts",
    versioning=aws.s3.BucketVersioningArgs(enabled=True),
    tags={"Environment": "dev", "ManagedBy": "Pulumi"},
)

# ── Security Group ───────────────────────────────────────────────────────────
sg = aws.ec2.SecurityGroup(
    "web-sg",
    description="Allow SSH and HTTP",
    ingress=[
        aws.ec2.SecurityGroupIngressArgs(protocol="tcp", from_port=22, to_port=22, cidr_blocks=["0.0.0.0/0"]),
        aws.ec2.SecurityGroupIngressArgs(protocol="tcp", from_port=80, to_port=80, cidr_blocks=["0.0.0.0/0"]),
    ],
    egress=[
        aws.ec2.SecurityGroupEgressArgs(protocol="-1", from_port=0, to_port=0, cidr_blocks=["0.0.0.0/0"]),
    ],
    tags={"ManagedBy": "Pulumi"},
)

# ── EC2 Instance ─────────────────────────────────────────────────────────────
instance = aws.ec2.Instance(
    "app-server",
    ami=ami_id,
    instance_type=instance_type,
    vpc_security_group_ids=[sg.id],
    user_data="""#!/bin/bash
dnf install -y nginx
systemctl enable --now nginx""",
    tags={"Name": "rhel8-demo-server", "ManagedBy": "Pulumi"},
)

# ── Exports ──────────────────────────────────────────────────────────────────
pulumi.export("bucket_name", bucket.bucket)
pulumi.export("instance_public_ip", instance.public_ip)
pulumi.export("instance_id", instance.id)
EOF

Step 4 — Preview and Deploy the Stack

Run pulumi preview to see a dry-run of all planned changes before committing them with pulumi up.

# Preview changes — no resources are created yet
pulumi preview

# Deploy the stack (creates real AWS resources)
pulumi up

# Pulumi will prompt for confirmation; type "yes" to proceed

# View the stack outputs after deployment
pulumi stack output

# Inspect the full state of all resources in the stack
pulumi stack --show-urns

# Refresh the state from live AWS (detects drift)
pulumi refresh

Step 5 — Manage Stacks for Multiple Environments

Pulumi stacks map cleanly to environments. Create a production stack, override configuration values for it, and switch between stacks without touching the program code.

# List existing stacks
pulumi stack ls

# Create a production stack
pulumi stack init prod

# Set production-specific config values
pulumi config set instanceType t3.small --stack prod
pulumi config set amiId ami-0c02fb55956c7d316 --stack prod

# Switch back to dev stack
pulumi stack select dev

# View config for the current stack
pulumi config

# Tag the stack for easier identification
pulumi stack tag set team ops
pulumi stack tag set project rhel8-demo

Step 6 — Tear Down Resources with pulumi destroy

When you no longer need the resources, destroy them cleanly to avoid ongoing AWS costs. Pulumi removes resources in the correct dependency order.

# Preview what will be destroyed
pulumi destroy --preview

# Destroy all resources in the current stack
pulumi destroy

# Remove the stack metadata once resources are destroyed
pulumi stack rm dev

# Comparing Pulumi vs Terraform:
# Pulumi: full programming language, loops, functions, type checking
# Terraform: HCL DSL, broad provider ecosystem, mature state backends
# Both support AWS, Azure, GCP; Pulumi has edge in complex conditional logic
echo "Resources destroyed and stack removed."

Conclusion

You have installed Pulumi on RHEL 8, written a Python program that provisions an S3 bucket and an EC2 instance with a security group, and used pulumi preview and pulumi up to deploy the infrastructure safely. Stack management lets you reuse the same codebase across development and production by varying only the configuration values. Pulumi’s general-purpose language support makes it particularly powerful for infrastructure that involves dynamic resource counts, shared helper functions, or existing Python libraries for generating configuration data.

Next steps: Managing Kubernetes Resources with Pulumi on RHEL 8, Using Pulumi Secrets for Sensitive Configuration, and Integrating Pulumi into a Jenkins CI/CD Pipeline.