Pulumi Deployment#

InfraGuard supports Pulumi as an alternative to Terraform for infrastructure provisioning. Both engines create identical resources (VPS, firewall, SSH key) and produce the same outputs.

Prerequisites#

  1. Install the Pulumi CLI
  2. Install Python 3.12+ (Pulumi runs the InfraGuard program with the Python runtime)
  3. Install the provider’s Pulumi SDK: pip install pulumi-digitalocean, pulumi-aws, pulumi-azure-native, or pulumi-hcloud
  4. Set the provider’s credential environment variables (see Cloud Credentials)

Quick Start#

infraguard deploy run \
  --domain cdn.example.com \
  --upstream https://10.0.0.5:8443 \
  --provider do \
  --engine pulumi \
  --c2-profile profiles/jquery.profile \
  --ssh-key ~/.ssh/id_ed25519.pub \
  --operator-ip 1.2.3.4/32

The --engine pulumi flag is the only difference from a Terraform deployment. All other flags are the same.

How It Works#

The PulumiProvider class wraps the Pulumi CLI with the same interface as the Terraform provider:

  1. Stage: Copies the Pulumi project from deploy/pulumi/ into the working directory
  2. Init stack: Creates (or selects) a Pulumi stack named from the domain
  3. Set config: Maps CLI flags to infraguard:* config keys
  4. Apply: Runs pulumi up --yes
  5. Outputs: Reads instance_ip, instance_id, ssh_user, and other outputs from the stack

After provisioning, the deploy lifecycle is the same as Terraform: the CLI waits for bootstrap, SCPs config and profile files, starts Docker Compose, and polls the health endpoint.

State Management#

Pulumi uses a local file backend. State files live in the deploy working directory (.infraguard-deploy/ by default). No remote backend or Pulumi Cloud account is required.

PULUMI_BACKEND_URL=file:///path/to/work_dir
PULUMI_CONFIG_PASSPHRASE=""
PULUMI_SKIP_UPDATE_CHECK=true

These are set automatically by the CLI. You do not need to export them.

Unlike Terraform, Pulumi state is not age-encrypted after apply. The --state-key and --state-identity flags are silently ignored when using --engine pulumi. If you need state encryption, use Pulumi’s built-in secrets provider or a remote backend.

Supported Providers#

ProviderAliasPulumi PackageSSH User
DigitalOceandopulumi-digitaloceanroot
AWSawspulumi-awsubuntu
Azureazurepulumi-azure-nativeoperator
Hetznerhzpulumi-hcloudroot

Cloudflare Workers deployment is Terraform-only. There is no Pulumi equivalent.

Destroy#

infraguard deploy destroy --engine pulumi

Selects the active stack in the working directory and runs pulumi destroy --yes.

Rotation#

infraguard rotate \
  --domain cdn.example.com \
  --upstream https://10.0.0.5:8443 \
  --provider do \
  --engine pulumi \
  --c2-profile profiles/jquery.profile \
  --ssh-key ~/.ssh/id_ed25519.pub \
  --operator-ip 1.2.3.4/32

Blue-green rotation works identically with Pulumi. The green instance is provisioned with Pulumi, health-checked, and the blue instance is destroyed with Pulumi.

Differences from Terraform#

TerraformPulumi
State formatJSON .tfstatePulumi checkpoint JSON
State encryptionage-encrypted by CLINot encrypted (use Pulumi secrets provider)
DO SSH keyMust pre-register on accountCreated automatically
LanguageHCL .tf filesPython
Binaryterraformpulumi
BackendLocal directoryLocal file backend

Project Structure#

The Pulumi project lives in deploy/pulumi/:

deploy/pulumi/
  Pulumi.yaml              # Project definition with config schema
  Pulumi.example.yaml      # Example stack config
  requirements.txt         # Python dependencies
  __main__.py              # Entrypoint: reads config, dispatches to provider
  cloud_init.py            # Shared bootstrap script
  provider_digitalocean.py # DO: Droplet + Tag + Firewall + SshKey
  provider_aws.py          # AWS: EC2 + SecurityGroup + KeyPair
  provider_azure.py        # Azure: RG + VNet + Subnet + PIP + NSG + NIC + VM
  provider_hetzner.py      # Hetzner: Server + Firewall + SshKey