How to Configure Infrastructure as Code with Pulumi on RHEL 7

Infrastructure as Code (IaC) transforms manual cloud provisioning into version-controlled, repeatable programs. While tools like Terraform use a domain-specific language, Pulumi takes a different approach: you write infrastructure definitions in real general-purpose languages — Python, TypeScript, Go, or C# — giving you the full power of loops, conditionals, functions, and unit tests. On RHEL 7, installing Pulumi is a single curl command, and from that point forward your cloud resources are defined in the same language your application developers already use. This tutorial covers installing the Pulumi CLI, initialising a new project with both TypeScript and Python templates, understanding Pulumi.yaml, writing real resources, running pulumi up, pulumi preview, and pulumi destroy, comparing state backends, and using stack configurations and secrets to manage environment-specific values securely.

Prerequisites

  • RHEL 7 server with sudo or root access
  • Internet access to reach Pulumi’s CDN and the Pulumi Service (or an S3 bucket for self-hosted state)
  • A cloud provider account — examples use AWS; adapt as needed for GCP or Azure
  • AWS CLI configured with aws configure (credentials in ~/.aws/credentials)
  • Node.js 16+ for TypeScript projects (sudo yum install -y nodejs) or Python 3.6+ for Python projects
  • A Pulumi account at app.pulumi.com for the default managed state backend, or an S3 bucket for self-hosted state

Step 1: Install the Pulumi CLI

Pulumi provides an official installer script that detects your OS and downloads the appropriate binary to ~/.pulumi/bin:

curl -fsSL https://get.pulumi.com | sh

Add Pulumi to your PATH for the current session and permanently:

export PATH="$HOME/.pulumi/bin:$PATH"
echo 'export PATH="$HOME/.pulumi/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

# Verify installation
pulumi version
# v3.115.2

Log in to the Pulumi Service (free tier available) to use the managed state backend:

pulumi login
# Opens a browser for OAuth, or use an access token:
pulumi login --non-interactive --access-token pul-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Step 2: Create a New Project with pulumi new

The pulumi new command scaffolds a project from a template. Pulumi maintains official templates for every supported language and cloud provider.

Python Project (AWS)

mkdir ~/pulumi-demo && cd ~/pulumi-demo
pulumi new aws-python --name my-infra --stack dev --description "Demo RHEL 7 infrastructure"

The wizard prompts for the AWS region. After completion, the directory contains:

my-infra/
├── Pulumi.yaml          # Project metadata
├── Pulumi.dev.yaml      # Stack configuration for the 'dev' stack
├── __main__.py          # Your infrastructure code
├── requirements.txt     # Python dependencies
└── venv/                # Auto-created virtual environment

TypeScript Project (AWS)

mkdir ~/pulumi-ts-demo && cd ~/pulumi-ts-demo
pulumi new aws-typescript --name my-infra-ts --stack staging

This generates index.ts, package.json, and tsconfig.json alongside the Pulumi files.

Step 3: Understanding Pulumi.yaml

The Pulumi.yaml file is the project descriptor. A minimal example:

name: my-infra
runtime:
  name: python
  options:
    virtualenv: venv
description: Demo infrastructure for RHEL 7 tutorial
config:
  pulumi:tags:
    value:
      pulumi:template: aws-python

The runtime field tells Pulumi which language runtime to use. For TypeScript projects it is nodejs; for Python it is python. The optional virtualenv option instructs Pulumi to activate the venv before running the program.

Step 4: Writing Resources in Python

Edit __main__.py to define real AWS infrastructure. The following example creates a VPC, a public subnet, an internet gateway, a security group, and an EC2 instance:

import pulumi
import pulumi_aws as aws

# Read config values
config = pulumi.Config()
instance_type = config.get("instanceType") or "t3.micro"
ami_id = config.require("amiId")  # Will error if not set in stack config

# VPC
vpc = aws.ec2.Vpc("demo-vpc",
    cidr_block="10.0.0.0/16",
    enable_dns_hostnames=True,
    enable_dns_support=True,
    tags={"Name": "demo-vpc", "Environment": pulumi.get_stack()})

# Public subnet
subnet = aws.ec2.Subnet("demo-subnet",
    vpc_id=vpc.id,
    cidr_block="10.0.1.0/24",
    availability_zone="us-east-1a",
    map_public_ip_on_launch=True,
    tags={"Name": "demo-public-subnet"})

# Internet gateway
igw = aws.ec2.InternetGateway("demo-igw",
    vpc_id=vpc.id,
    tags={"Name": "demo-igw"})

# Route table
route_table = aws.ec2.RouteTable("demo-rt",
    vpc_id=vpc.id,
    routes=[aws.ec2.RouteTableRouteArgs(
        cidr_block="0.0.0.0/0",
        gateway_id=igw.id,
    )],
    tags={"Name": "demo-public-rt"})

aws.ec2.RouteTableAssociation("demo-rta",
    subnet_id=subnet.id,
    route_table_id=route_table.id)

# Security group: allow SSH and HTTP
sg = aws.ec2.SecurityGroup("demo-sg",
    vpc_id=vpc.id,
    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"]),
    ])

# EC2 instance
instance = aws.ec2.Instance("demo-instance",
    ami=ami_id,
    instance_type=instance_type,
    subnet_id=subnet.id,
    vpc_security_group_ids=[sg.id],
    tags={"Name": f"demo-{pulumi.get_stack()}"})

# Export outputs
pulumi.export("instance_public_ip", instance.public_ip)
pulumi.export("instance_id", instance.id)
pulumi.export("vpc_id", vpc.id)

Step 5: Stack Configuration and Secrets

Stack-specific values live in Pulumi.dev.yaml. Set them via the CLI:

# Plain config value
pulumi config set instanceType t3.small

# Required AMI ID (RHEL 7 in us-east-1)
pulumi config set amiId ami-0c322300a1dd460e7

# Encrypted secret — never stored in plaintext
pulumi config set --secret dbPassword "s3cr3t-p@ssword"

# View the stack config (secrets are masked)
cat Pulumi.dev.yaml

The Pulumi.dev.yaml file is safe to commit to version control for non-secret values. Secret values are stored encrypted using a stack-specific encryption key managed by the Pulumi Service or your own KMS key.

Step 6: Previewing and Deploying with pulumi up

# Install Python dependencies
source venv/bin/activate
pip install -r requirements.txt

# Preview the changes without applying them
pulumi preview

# Deploy the stack
pulumi up

Pulumi displays a diff showing resources to create, update, or delete, and prompts for confirmation before applying. The pulumi preview command is non-destructive and ideal for CI pipelines to validate changes before merging a pull request.

Step 7: State Management — Pulumi Service vs S3

Pulumi stores a state file (similar to Terraform’s terraform.tfstate) that maps your program’s resource declarations to real cloud objects. The default backend is the Pulumi Service at app.pulumi.com, which provides state locking, history, and a web dashboard. For air-gapped or compliance-sensitive environments, you can use an S3 bucket as the state backend:

# Log out of Pulumi Service and switch to S3 backend
pulumi logout
pulumi login s3://my-company-pulumi-state

# Migrate an existing stack's state to S3
pulumi stack export --file stack-state.json
pulumi stack import --file stack-state.json

With the S3 backend, you are responsible for enabling versioning and object locking on the bucket to prevent concurrent state corruption.

Step 8: Destroying Resources

# Preview what will be destroyed
pulumi destroy --skip-preview

# Full interactive destroy with confirmation
pulumi destroy

# Remove the stack record after resources are gone
pulumi stack rm dev

Pulumi on RHEL 7 brings the full expressiveness of Python or TypeScript to infrastructure provisioning. Because your infrastructure is real code, you can write unit tests with pytest or jest, extract reusable components into Python classes or TypeScript modules, and apply standard code review workflows to infrastructure changes. The stack-based model cleanly separates dev, staging, and production environments with environment-specific configuration, while the secrets system ensures credentials never appear in plaintext in source control. As your infrastructure grows, explore Pulumi’s component resources and policy-as-code (CrossGuard) for enforcing organisational standards across all stacks.