
Pulumi Arm To Pulumi
Convert existing Azure Resource Manager or Bicep templates into maintainable Pulumi TypeScript so you can manage Azure infra with code your agent already understands.
Overview
pulumi-arm-to-pulumi is an agent skill for the Operate phase that converts Azure ARM or Bicep template constructs into Pulumi TypeScript using documented translation patterns.
Install
npx skills add https://github.com/pulumi/agent-skills --skill pulumi-arm-to-pulumiWhat is this skill?
- Side-by-side ARM JSON to Pulumi TypeScript patterns for common Azure resources (e.g. storage accounts)
- Covers ARM parameters, apiVersion, and property naming mapped to @pulumi/azure-native types
- Includes guidance for Azure Classic provider scenarios where templates still reference legacy APIs
- Default prompt wires the skill into agent workflows for template migration and import workflows
- Reference-oriented: basic resource conversion plus extended construct patterns in the full skill body
Adoption & trust: 1.7k installs on skills.sh; 56 GitHub stars; 2/3 security scanners passed (skills.sh audits).
What problem does it solve?
You have working ARM or Bicep templates but need Pulumi TypeScript your team and coding agents can evolve without re-authoring every Azure resource by hand.
Who is it for?
Indie builders or small teams moving Azure workloads from ARM/Bicep to Pulumi while keeping resource names, SKUs, and security properties consistent.
Skip if: Greenfield Azure apps with no templates to migrate, or teams that will stay on Bicep-only workflows without Pulumi.
When should I use this skill?
Use $pulumi-arm-to-pulumi to migrate Azure ARM or Bicep templates and imports to Pulumi.
What do I get? / Deliverables
You get aligned Pulumi TypeScript snippets and migration patterns you can paste into a Pulumi project and validate with plan/up before decommissioning the original templates.
- Pulumi TypeScript resource blocks equivalent to ARM constructs
- pulumi.Config mappings for former ARM parameters
- Migration notes for Classic provider edge cases
Recommended Skills
Journey fit
IaC migration and ongoing Azure resource management sit in Operate once you are running or refactoring production infrastructure, not in initial product ideation. Infra is the canonical shelf for ARM/Bicep-to-Pulumi translation because the skill maps resource definitions, parameters, and imports into deployable Pulumi programs.
How it compares
Use as a conversion-pattern skill for IaC translation, not as a one-click deploy MCP or a generic Azure CLI cheat sheet.
Common Questions / FAQ
Who is pulumi-arm-to-pulumi for?
Solo builders and small teams on Azure who already have ARM or Bicep and want Pulumi TypeScript maintainable by Claude Code, Cursor, or Codex.
When should I use pulumi-arm-to-pulumi?
During Operate infra work when you refactor stacks, import existing resources, or standardize on Pulumi after an acquisition or template handoff.
Is pulumi-arm-to-pulumi safe to install?
Treat it as documentation that may guide code changes affecting live Azure resources; review the Security Audits panel on this Prism page before running generated deploy commands.
SKILL.md
READMESKILL.md - Pulumi Arm To Pulumi
interface: display_name: "ARM/Bicep to Pulumi Migration" short_description: "Convert Azure ARM or Bicep templates to Pulumi" default_prompt: "Use $pulumi-arm-to-pulumi to migrate Azure ARM or Bicep templates and imports to Pulumi." # ARM → Pulumi Conversion Patterns Reference guide for translating ARM template constructs to Pulumi TypeScript. Covers all common patterns plus Azure Classic provider examples. ## Basic Resource Conversion **ARM Template:** ```json { "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2023-01-01", "name": "[parameters('storageAccountName')]", "location": "[parameters('location')]", "sku": { "name": "Standard_LRS" }, "kind": "StorageV2", "properties": { "supportsHttpsTrafficOnly": true } } ``` **Pulumi TypeScript:** ```typescript import * as pulumi from "@pulumi/pulumi"; import * as azure_native from "@pulumi/azure-native"; const config = new pulumi.Config(); const storageAccountName = config.require("storageAccountName"); const location = config.require("location"); const resourceGroupName = config.require("resourceGroupName"); const storageAccount = new azure_native.storage.StorageAccount("storageAccount", { accountName: storageAccountName, location: location, resourceGroupName: resourceGroupName, sku: { name: azure_native.storage.SkuName.Standard_LRS, }, kind: azure_native.storage.Kind.StorageV2, enableHttpsTrafficOnly: true, }); ``` ## ARM Parameters → Pulumi Config **ARM Template:** ```json { "parameters": { "location": { "type": "string", "defaultValue": "eastus", "metadata": { "description": "Location for resources" } }, "instanceCount": { "type": "int", "defaultValue": 2, "minValue": 1, "maxValue": 10 }, "enableBackup": { "type": "bool", "defaultValue": true }, "secretValue": { "type": "securestring" } } } ``` **Pulumi TypeScript:** ```typescript const config = new pulumi.Config(); const location = config.get("location") || "eastus"; const instanceCount = config.getNumber("instanceCount") || 2; const enableBackup = config.getBoolean("enableBackup") ?? true; const secretValue = config.requireSecret("secretValue"); // Returns Output<string> ``` ## ARM Variables → Pulumi Variables **ARM Template:** ```json { "variables": { "storageAccountName": "[concat('storage', uniqueString(resourceGroup().id))]", "webAppName": "[concat(parameters('prefix'), '-webapp')]" } } ``` **Pulumi TypeScript:** ```typescript import * as pulumi from "@pulumi/pulumi"; const config = new pulumi.Config(); const prefix = config.require("prefix"); const resourceGroupId = config.require("resourceGroupId"); // Simple variable const webAppName = `${prefix}-webapp`; // ARM's uniqueString() produces a deterministic 13-char hash — use a fixed suffix // from config or a truncated ID as an approximation: // const storageAccountName = `storage${resourceGroupId.substring(0, 8)}`.toLowerCase(); // Note: not cryptographically equivalent to ARM's uniqueString() const storageAccountName = `storage${resourceGroupId}`.toLowerCase(); ``` ## ARM Copy Loops → Pulumi Loops **ARM Template:** ```json { "type": "Microsoft.Network/virtualNetworks/subnets", "apiVersion": "2023-05-01", "name": "[concat(variables('vnetName'), '/subnet-', copyIndex())]", "copy": { "name": "subnetCopy", "count": "[parameters('subnetCount')]" }, "properties": { "addressPrefix": "[concat('10.0.', copyIndex(), '.0/24')]" } } ``` **Pulumi TypeScript:** ```typescript const config = new pulumi.Config(); const subnetCount = config.getNumber("subnetCount") || 3; const subnets: azure_native.network.Subnet[] = []; for (let i = 0; i < subnetCount; i++) { subnets.push(new azure_native.network.Subnet(`subnet-${i}`, { subnetName: `subnet-${i}`, virtualNetworkName: vnet.name, resourceGroupName: resourceGroup