Communitygithub.com

Pulumi Infraestructura como Código

Infraestructura como Código nativa en TypeScript con Pulumi.

¿Qué es Pulumi Infraestructura como Código?

Este skill proporciona una guía completa para usar Pulumi con TypeScript para definir y gestionar infraestructura cloud de forma programática. Aprovecha la seguridad de tipos de TypeScript, la reutilización mediante ComponentResource y el autodescubrimiento de dependencias. Cubre patrones esenciales como la definición de recursos, el manejo de Outputs (usando pulumi.interpolate en lugar de concatenación), la gestión de secretos con Config.requireSecret, las referencias entre stacks, las transformaciones (transforms) y la Automation API. Está diseñado para equipos que buscan construir infraestructura auto-gestionada, cumplir con políticas de compliance mediante CrossGuard y mantener un código mantenible y predecible. El skill advierte explícitamente sobre anti-patrones críticos como crear recursos dentro de .apply(), omitir { parent: this } en componentes, o usar concatenación de strings con Outputs.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/agents-inc/skills/tree/HEAD/src/skills/infra-iac-pulumi

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

¿Qué hace Pulumi Infraestructura como Código?

Guía rápida: Define infraestructura cloud en TypeScript con seguridad de tipos total. Usa ComponentResource para encapsular patrones reutilizables de infraestructura. Pasa { parent: this } a todos los recursos hijos dentro de componentes. Usa pulumi.interpolate para construir cadenas con Outputs (no concatenación de strings). Nunca crees recursos dentro de .apply(). Usa Config.requireSecret() para valores sensibles. Prefiere transforms sobre las obsoletas transformations. Siempre llama a this.registerOutputs() al final de los constructores de componentes.


<critical_requirements>

CRÍTICO: Antes de Usar Este Skill

Todo el código debe seguir las convenciones del proyecto en CLAUDE.md (kebab-case, exportaciones nombradas, orden de importaciones, import type, constantes nombradas)

(DEBES pasar { parent: this } a TODOS los recursos hijos dentro de un ComponentResource — omitirlo rompe el árbol de recursos y el seguimiento de estado)

(DEBES usar pulumi.interpolate para construir cadenas con Outputs — la concatenación de strings produce silenciosamente [object Object])

(NUNCA debes crear recursos dentro de .apply() — no aparecerán en pulumi preview y causarán problemas de orden)

(DEBES usar Config.requireSecret() para valores sensibles — Config.require() almacena valores como texto plano en el estado)

(DEBES llamar a this.registerOutputs() al final de cada constructor de ComponentResource — omitirlo impide el seguimiento de outputs)

</critical_requirements>


Recursos detallados:

  • examples/core.md - Definiciones de recursos, recursos de componente, nomenclatura, Outputs, configuración/secretos
  • examples/advanced.md - Referencias entre stacks, transformaciones, proveedores dinámicos, Automation API, policy packs
  • reference.md - Marcos de decisión, tabla de opciones de recursos, referencia de API, comandos CLI

Auto-detección: Pulumi, @pulumi/pulumi, @pulumi/aws, @pulumi/gcp, @pulumi/azure, @pulumi/kubernetes, pulumi.ComponentResource, pulumi.CustomResource, pulumi.Output, pulumi.Config, pulumi.interpolate, pulumi.all, registerOutputs, StackReference, ComponentResourceOptions, CustomResourceOptions, dynamic.Resource, dynamic.ResourceProvider, LocalWorkspace, InlineProgramArgs, Automation API, CrossGuard, PolicyPack

Cuándo usarlo:

  • Definir infraestructura cloud en TypeScript con APIs de recursos con seguridad de tipos
  • Crear componentes reutilizables de infraestructura con ComponentResource
  • Gestionar arquitecturas multi-stack con referencias entre stacks
  • Manejar secretos y configuración específica del entorno
  • Construir plataformas de infraestructura autogestionadas con la Automation API
  • Escribir políticas de cumplimiento con policy packs de CrossGuard

Cuándo NO usarlo:

  • Scripts únicos que crean un solo recurso (usa directamente la CLI cloud)
  • Proyectos donde el equipo no tiene experiencia en TypeScript (considera otras opciones de IaC)

Patrones clave cubiertos:

  • Definiciones de recursos con entradas tipadas y nomenclatura automática
  • Encapsulación con ComponentResource (parent, naming, registerOutputs)
  • Outputs: apply, all, interpolate y lifting
  • Configuración y gestión de secretos (Config.require, Config.requireSecret, pulumi.secret)
  • Referencias entre stacks para compartir datos entre stacks
  • Opciones de recursos (dependsOn, protect, aliases, ignoreChanges, transforms)
  • Proveedores dinámicos para recursos CRUD personalizados
  • Automation API para gestión programática de stacks
  • Policy packs de CrossGuard para cumplimiento normativo

Filosofía

Pulumi trata la infraestructura como código real, no como archivos de configuración. TypeScript te brinda seguridad de tipos, autocompletado en IDE, herramientas de refactorización y todo el ecosistema de Node.js. Los recursos son objetos, las dependencias son automáticas y la reutilización ocurre a través de funciones y clases, no un lenguaje de módulos personalizado.

Principios fundamentales:

  • Los recursos son objetos: Cada recurso cloud es una instancia de una clase TypeScript con entradas y salidas tipadas
  • Las dependencias son automáticas: Cuando pasas la salida de un recurso como entrada de otro, Pulumi infiere el grafo de dependencias
  • Reutilización mediante componentes: ComponentResource encapsula múltiples recursos en una unidad lógica única con sus propias entradas y salidas
  • Los Outputs son promesas: Output<T> representa un valor que puede no conocerse hasta después del despliegue — usa apply, all o interpolate para trabajar con ellos, nunca los desempaques manualmente
  • El estado se gestiona: Pulumi rastrea cada recurso en el estado — cambiar un nombre lógico o mover un recurso entre archivos provoca un borrado y recreación a menos que uses aliases

Patrones Principales

Patrón 1: Definiciones de Recursos

Cada recurso toma un nombre lógico (usado para el seguimiento de estado), un conjunto de argumentos (entradas tipadas) y opciones de recurso opcionales.

const bucket = new aws.s3.Bucket(
  "data-bucket",
  {
    versioning: { enabled: true },
    lifecycleRules: [
      { enabled: true, expiration: { days: BUCKET_EXPIRY_DAYS } },
    ],
  },
  { protect: true },
); // Prevenir borrado accidental

Puntos clave: Los nombres lógicos deben ser únicos por tipo dentro de un stack. Pulumi añade automáticamente un sufijo aleatorio al nombre físico para evitar colisiones. Usa protect: true en recursos críticos. Exporta outputs para consumo entre stacks.

Consulta examples/core.md para nomenclatura de recursos, configuración de nomenclatura automática y opciones de proveedor.


Patrón 2: Encapsulación con ComponentResource

Agrupa recursos relacionados en un ComponentResource para crear unidades reutilizables de infraestructura.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

interface StaticSiteArgs {
  indexDocument?: string;
  errorDocument?: string;
}

class StaticSite extends pulumi.ComponentResource {
  public readonly bucketName: pulumi.Output<string>;
  public readonly websiteUrl: pulumi.Output<string>;

  constructor(
    name: string,
    args: StaticSiteArgs,
    opts?: pulumi.ComponentResourceOptions,
  ) {
    super("myinfra:web:StaticSite", name, args, opts);

    const bucket = new aws.s3.BucketV2(`${name}-bucket`, {}, { parent: this });

    const website = new aws.s3.BucketWebsiteConfigurationV2(
      `${name}-website`,
      {
        bucket: bucket.id,
        indexDocument: { suffix: args.indexDocument ?? "index.html" },
        errorDocument: { key: args.errorDocument ?? "error.html" },
      },
      { parent: this },
    );

    this.bucketName = bucket.id;
    this.websiteUrl = website.websiteEndpoint;
    this.registerOutputs({
      bucketName: this.bucketName,
      websiteUrl: this.websiteUrl,
    });
  }
}

Por qué es bueno: los recursos hijos usan { parent: this }, el nombre se prefija desde el padre, se llama a registerOutputs, el token de tipo sigue el formato pkg:module:Type

Consulta examples/core.md para patrones completos de componentes con herencia de proveedores y recursos multi-componente.


Patrón 3: Trabajar con Outputs

Los Outputs representan valores resueltos después del despliegue. Nunca uses concatenación de strings — usa interpolate, apply o all.

// interpolate — plantilla etiquetada para construir cadenas (preferido)
const url = pulumi.interpolate`https://${bucket.bucketRegionalDomainName}/index.html`;

// apply — transforma un solo output
const upper = bucket.id.apply((id) => id.toUpperCase());

// all — combina múltiples outputs
const endpoint = pulumi
  .all([lb.dnsName, listener.port])
  .apply(([dns, port]) => `http://${dns}:${port}`);

// Lifting — accede a propiedades directamente en los outputs del recurso
const subnetId = vpc.subnets[0].id; // No se necesita apply para propiedades conocidas

Por qué es bueno: interpolate maneja valores Output de forma transparente, all espera múltiples valores, el lifting evita llamadas apply innecesarias

Trampa: Los recursos creados dentro de .apply() no aparecerán en pulumi preview y pueden causar problemas de orden. Siempre pasa Outputs directamente como entradas a otros recursos.

Consulta examples/core.md para patrones de Output, envoltura con pulumi.output() y el anti-patrón de apply.


Patrón 4: Configuración y Secretos

Usa pulumi.Config para valores específicos del stack. Usa requireSecret para datos sensibles — cifra el valor en el estado.

const config = new pulumi.Config();

// Valores de configuración planos
const region = config.require("region"); // Falla si falta
const nodeCount = config.getNumber("nodeCount"); // Devuelve undefined si falta

// Valores secretos — cifrados en el estado
const dbPassword = config.requireSecret("dbPassword");
const apiKey = config.getSecret("apiKey");

// Marcar valores programáticos como secretos
const connectionString = pulumi.interpolate`postgres://admin:${dbPassword}@${db.endpoint}/mydb`;
// connectionString es automáticamente secreto porque dbPassword es secreto

// Marcar explícitamente un valor como secreto
const token = pulumi.secret(generateToken());

Punto clave: Cualquier Output derivado de un secreto se marca automáticamente como secreto. No necesitas volver a marcar los valores derivados.

Consulta examples/core.md para configuración con espacios de nombres, outputs secretos y comandos CLI de configuración.


Patrón 5: Opciones de Recursos

Las opciones de recursos controlan el comportamiento del ciclo de vida. Las más importantes:

const db = new aws.rds.Instance(
  "primary-db",
  {
    /* ... */
  },
  {
    protect: true, // Prevenir borrado accidental
    dependsOn: [vpc, securityGroup], // Orden explícito
    ignoreChanges: ["tags"], // Ignorar deriva en props específicos
    aliases: [{ name: "old-db-name" }], // Renombrar sin recrear
    retainOnDelete: true, // Mantener recurso cloud en pulumi destroy
    deleteBeforeReplace: true, // Para recursos que deben ser únicos
    replaceOnChanges: ["engine"], // Forzar reemplazo en cambios específicos
    provider: usEastProvider, // Proveedor explícito (región, cuenta)
  },
);

Trampa: Cambiar el nombre lógico o el padre de un recurso hace que Pulumi lo borre y lo recree. Usa aliases para renombrar de forma segura.

Consulta reference.md para la tabla completa de opciones de recursos.


Patrón 6: Referencias entre Stacks

Comparte outputs entre stacks usando StackReference.

// En el stack de red: exportar outputs
export const vpcId = vpc.id;
export const subnetIds = subnets.map((s) => s.id);

// En el stack de aplicación: consumir outputs
const networkStack = new pulumi.StackReference("myorg/networking/prod");
const vpcId = networkStack.getOutput("vpcId");
const subnetIds = networkStack.getOutput("subnetIds");

// requireOutput falla si el output no existe (más seguro que getOutput)
const vpcIdRequired = networkStack.requireOutput("vpcId");

Consulta examples/advanced.md para patrones de referencia entre stacks y getOutputDetails.


Patrón 7: Transformaciones (Transforms)

Aplica transformaciones a los recursos y sus hijos. Usa transforms (no las obsoletas transformations). Transforms reciben un objeto args con type, props y opts, y devuelven un resultado modificado o undefined para omitir.

// A nivel de recurso: aplicar tags a todos los hijos etiquetables de un componente
const vpc = new MyVpcComponent(
  "vpc",
  {},
  {
    transforms: [
      (args) => {
        if (isTaggable(args.type)) {
          return {
            props: {
              ...args.props,
              tags: { ...args.props["tags"], ManagedBy: "pulumi" },
            },
            opts: args.opts,
          };
        }
        return undefined;
      },
    ],
  },
);

Diferencia clave con las obsoletas transformations: las transforms soportan modificar hijos de componentes empaquetados (awsx, eks), soportan callbacks asíncronos y no pasan un objeto Resource.

Consulta examples/advanced.md para transforms a nivel de stack, modificación de opciones y migración desde transformations.


Patrón 8: Automation API

Ejecuta Pulumi programáticamente sin la CLI — para plataformas autogestionadas, pruebas de integración o herramientas de despliegue personalizadas.

const stack = await LocalWorkspace.createOrSelectStack({
  stackName: "dev",
  projectName: "my-platform",
  program: async () => {
    const bucket = new aws.s3.Bucket("auto-bucket");
    return { bucketName: bucket.id };
  },
});
const upResult = await stack.up({ onOutput: console.log });

Punto clave: La Automation API requiere que la CLI de Pulumi esté instalada y en el PATH — usa el motor de la CLI internamente.

Consulta examples/advanced.md para preview, destroy, modo de programa local, configuración y obtención de outputs del stack.


<red_flags>

BANDERAS ROJAS

Alta Prioridad:

  • Crear recursos dentro de .apply() — No aparecerán en pulumi preview, causan problemas de orden y rompen el grafo de dependencias. Pasa Outputs directamente como entradas de recursos.
  • Falta de { parent: this } en hijos de ComponentResource — Los recursos aparecen en la raíz del árbol de estado, rompiendo la agrupación lógica y el borrado en cascada del componente.
  • Concatenación de strings con Outputs — "https://" + bucket.id produce [object Object]. Usa pulumi.interpolate en su lugar.
  • Usar Config.require() para contraseñas/claves — Almacena el valor como texto plano en el estado. Usa Config.requireSecret() para cifrar.
  • Olvidar this.registerOutputs() — Los outputs del componente no se rastrearán correctamente en el estado ni estarán disponibles mediante referencias entre stacks.
  • Cambiar nombres lógicos sin aliases — Pulumi borra y recrea el recurso. Usa aliases: [{ name: "old-name" }] para renombrar de forma segura.

Prioridad Media:

  • Confiar en proveedores predeterminados en configuraciones multi-región — Usa proveedores explícitos por región. Establece pulumi:disable-default-providers en la configuración para forzar.
  • Empezar con funciones en lugar de ComponentResource — Migrar más tarde requiere aliases o recreación de recursos. Usa componentes desde el principio.
  • No usar dependsOn para dependencias no obvias — Pulumi infiere dependencias de la conexión Input/Output, pero efectos secundarios (propagación de IAM, etc.) pueden requerir dependsOn.

</red_flags>


Ejemplos de Comandos y Uso

Comandos CLI

# Inicializar un nuevo proyecto
pulumi new typescript --name my-infra --stack dev

# Configurar valores
pulumi config set aws:region us-east-1
pulumi config set dbPassword --secret

# Desplegar
pulumi up --stack dev

# Obtener outputs
pulumi stack output endpoint

# Destruir
pulumi destroy --stack dev

Declaración de importaciones

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

Exportar outputs

export const url = pulumi.interpolate`https://${bucket.bucketRegionalDomainName}`;
export const arn = bucket.arn;

Manejo de secretos

const config = new pulumi.Config();
export const dbPassword = config.requireSecret("dbPassword");
export const connectionString = pulumi.interpolate`postgres://admin:${dbPassword}@${db.endpoint}/mydb`;

Pattern típico de componente

class MyComponent extends pulumi.ComponentResource {
  constructor(name: string, opts?: pulumi.ComponentResourceOptions) {
    super("pkg:module:MyComponent", name, {}, opts);
    // Crear recursos con { parent: this }
    this.registerOutputs({});
  }
}

Guía de Inicio Rápido

  1. Instalar Pulumi: curl -fsSL https://get.pulumi.com | sh
  2. Inicializar proyecto: pulumi new typescript en tu directorio de infraestructura
  3. Configurar proveedor cloud: Establece las credenciales y región adecuadas (ej. pulumi config set aws:region us-east-1)
  4. Definir recursos: Sigue los patrones de ComponentResource para estructurar tu infraestructura
  5. Gestionar secretos: Usa siempre Config.requireSecret() para valores sensibles
  6. Desplegar: pulumi up para revisar y desplegar los cambios

Consejos para empezar:

  • Revisa los ejemplos en los archivos markdown vinculados para ver patrones completos
  • Usa el autocompletado de TypeScript para explorar las APIs de los recursos
  • Comienza con un stack pequeño y ve añadiendo complejidad gradualmente
  • Configura un pipeline CI/CD con la Automation API para despliegues automatizados

Individual skills in this repo

This repo contains 11 individual skills — each has its own dedicated page.

Skills relacionados