Author and deploy AWS infrastructure with SST v4 and Pulumi
An SST v4 (Ion) skill for authoring, testing, and safely migrating Pulumi-backed AWS infrastructure, with incident-informed conventions.
Why it matters
Define, test, and deploy production-grade AWS infrastructure as TypeScript code using the SST v4 (Ion) framework with Pulumi, following battle-tested patterns for resource authoring, linking, testing, and troubleshooting that prevent common deployment failures.
Outcomes
What it gets done
Write sst.config.ts and infra/ modules with typed sst.aws.* components (Function, Bucket, Dynamo, Cron, Service, Router) and raw Pulumi aws.* resources
Wire resource links between modules using SST link, SSM Parameter Store, and IAM scopes while avoiding Output interpolation bugs
Create source-level Vitest tests that assert resource invariants and prevent silent regressions during infrastructure changes
Deploy stacks safely with stage-gated lifecycle policies, migrate resources between Pulumi types, and troubleshoot failed deployments
Install
Add it to your toolbox
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/ag-aws-sst-development | bash Overview
SST v4 for AWS
This skill covers SST v4 (Ion) development: authoring Pulumi-backed AWS resources, correct runtime and Output interpolation patterns, source-level testing, and safe two-PR migrations between Pulumi resource types. Use it for SST v4 (Ion) config, infra modules, resource linking, deploys, or migrations. Does not apply to SST Classic (v2/v3, CDK-based), a different framework.
What it does
This skill provides expert guidance for SST v4, the Ion engine, a Pulumi-backed infrastructure-as-code framework where AWS resources are described in TypeScript via high-level sst.aws.* components such as Function, Bucket, Dynamo, Cron, Service, and Router, or raw aws.* Pulumi resources for the long tail, plus sst.Secret and sst.Linkable for wiring. It covers authoring sst.config.ts and infra/ modules, resource linking, testing, deployment, and troubleshooting, encoding lessons distilled from real multi-stack production projects. Since SST and Pulumi are third-party and evolve, it insists on verifying current component syntax via Context7 and AWS-side facts like service limits, IAM action names, and region availability via the AWS docs MCP rather than from memory. Before editing, it orients on the repo: reading sst.config.ts for app name, region, providers, and default tags, and the run() import order, which is the dependency order, checking for project-specific IaC rules, checking existing Vitest invariants that must stay green, and confirming the actual SST and Pulumi versions and package manager, running a version check to confirm v4/Ion rather than the CDK-based v2/v3 SST Classic.
Universal conventions that apply to any SST v4 and AWS project include controlling the Node runtime deliberately in one place via a global transform rather than trusting SST's default; never interpolating a Pulumi Output into a plain JavaScript template literal, since a bare interpolation type-checks and runs fine in dev mode but produces a broken ARN that only fails at deploy time and has caused real production outages; defaulting to two sequential pull requests when migrating a resource between Pulumi types, since Pulumi creates before destroying and uniqueness-constrained AWS names conflict otherwise; and preferring typed resources over the stringly-typed CloudControl escape hatch. Project-specific defaults worth adopting for consistency, but confirming per repo, include a fixed region and default tags, stage-gated removal and protect settings so production resources survive teardown, SSM Parameter Store as the out-of-graph contract for cross-app consumers while same-app Lambdas use SST's link mechanism for a real dependency edge with IAM, lazy dynamic imports of infra modules to keep hot-reload light, source-level Vitest tests asserting on resource names, shapes, and IAM scopes, and an observability gate requiring an alarm and structured logging on every new Lambda, queue, or schedule before merge.
When to use - and when NOT to
Use it when writing or editing sst.config.ts, building infra/ modules, wiring resource links, running or troubleshooting a deploy, or migrating a resource between Pulumi types on an SST v4 (Ion) project - not SST Classic (v2 or v3, CDK-based), which is a different framework these patterns don't apply to.
Inputs and outputs
Given an infrastructure change request, it produces authored SST and Pulumi resource code matching the repo's house style, source-level test assertions, and a deploy or migration plan - flagging any irreversible action such as a deploy, a teardown, or a type migration with the target AWS account before execution.
Integrations
Context7 for current SST and Pulumi component syntax, AWS documentation MCP for verified AWS facts, Vitest for source-level infrastructure tests, and the AWS CLI identity check to confirm the target account before deploying.
Who it's for
Teams running production SST v4 infrastructure who need house-style-consistent, incident-informed patterns for authoring, testing, and safely migrating Pulumi-backed AWS resources - distinguishing universal rules from project-specific conventions along the way.
Source README
SST v4 for AWS
When to Use
Use this skill when you need sST v4 (Ion) expert for managing AWS resources as code with the Pulumi-backed framework. Use when writing or editing sst.config.ts, building infra/ modules (sst.aws.Function/Bucket/Dynamo/Cron/Service/Router, sst.Secret, sst.Linkable, raw aws.* Pulumi resources), wiring resource links,...
SST v4 (the "Ion" engine) is a Pulumi-backed IaC framework: you describe AWS
resources in TypeScript and SST/Pulumi reconciles them into your account. It
gives you high-level sst.aws.* components (Function, Bucket, Dynamo, Cron,
Service, …) that expand into many underlying resources, plus an escape hatch to
any raw Pulumi aws.* resource for the long tail. This skill encodes a
production-proven way to author, link, test, deploy, and troubleshoot SST
stacks on AWS - distilled from real multi-stack projects that have paid for
each lesson with a prod incident.
SST and Pulumi are third-party - verify current syntax with Context7
(resolve-library-id → query-docs for sst or pulumi-aws) when you're
unsure about a component's options. Verify AWS-side facts (service limits,
model IDs, IAM action names, region availability) with the AWS docs MCP, never
from memory. The patterns here are the how; the docs are the what.
When you're invoked
Figure out which mode you're in and jump to the right reference:
| Situation | Go to |
|---|---|
| New project, or adding a resource/module to an existing SST app | Author → references/authoring.md |
| Wiring one module's output into another (links, SSM, IAM scope) | Author → references/authoring.md § Sharing |
| Writing tests for infra so changes don't silently break | Test → references/testing.md |
| Running a deploy, or a deploy just failed | Deploy/Operate → references/deploy-and-troubleshoot.md |
| Migrating a resource between Pulumi types, renaming a physical name | Deploy/Operate → references/deploy-and-troubleshoot.md § Migrations |
Always read the relevant reference before editing - they carry the why behind
each rule, which matters more than the rule itself.
Orientation: read the repo before you touch it
SST projects are conventional but not identical. Before editing, build a quick
map so your change matches the house style instead of fighting it:
sst.config.ts- the app name,home, providers/region,defaultTags,
any global$transform(Node runtime pin, bundle fixups), and the order in
whichrun()importsinfra/modules. The import order is the dependency
order; respect it.infra/- one file per domain (storage, functions, api, observability…).
This is where resources are declared. Check for aninfra/CLAUDE.md- these
projects keep IaC-specific rules there, and it's the single most valuable
file to read first.infra/tests/- source-level Vitest assertions that pin resource
invariants. If they exist, your change must keep them green and probably
needs a new assertion.package.json/.nvmrc- package manager (npm vs pnpm), Node version,
and thesst/pulumiversions actually installed.
Run npx sst version to confirm you're on v4/Ion (the $config + .sst/platform/
signature). v2/v3 ("SST Classic", CDK-based) is a different framework - these
patterns don't apply there.
The conventions, and which are universal vs tunable
The projects this skill is built from share a deliberate house style. Some of it
is universal (true for any SST v4 + AWS project - apply it everywhere); some
is project-specific (a sensible default these projects chose - adopt it for
consistency, but recognize a project may differ).
Universal - these principles hold for any SST v4 + AWS project:
- Control the Node runtime deliberately, in one place. Don't leave it to
whatever the installed SST happens to default to. The idiom is a single global$transform(sst.aws.Function, (args) => { args.runtime ??= "nodejs24.x" })inrun()-??=is correct here (the transform runs before the component
applies its own default, so it fills in only when the user didn't set one).
Recent SST already defaults to a current Node runtime, so check the installed
default first (Context7); the transform is then version-independence insurance
so a future SST downgrade can't silently move your fleet. Seereferences/authoring.md. - Never interpolate a Pulumi
Output<T>into a plain JS template literal.
Use$interpolate(orpulumi.interpolate). A bare top-level`${bucket.arn}/*`stringifies theOutputto a[Output<T>]placeholder
and produces a broken ARN that only fails at deploy time (it type-checks andsst devruns fine). The fix is$interpolate`${bucket.arn}/*`. This
has caused prod deploy outages. Seereferences/authoring.md§ Outputs. - Migrating a resource between Pulumi types should default to two PRs -
Pulumi creates-before-destroys, so for a uniqueness-constrained AWS name
(bucket, IAM role, gateway) the old resource still owns it and the create
fails withConflictException. Two sequential deploys (teardown, then
recreate) is the conservative default;aliases:/pulumi import/ state
surgery can bridge identity in some cases but only with a reviewed plan. Seereferences/deploy-and-troubleshoot.md§ Migrations. - Prefer typed
sst.aws.*/aws.*resources over theaws.cloudcontrol.Resourceescape hatch. CloudControl outputs are
stringly-typed andoneOffields don't patch cleanly. Use it only when no
typed resource exists yet, and migrate off it when one ships.
Project-specific defaults - adopt for consistency, but confirm per repo:
- Region
ap-northeast-1,home: "aws", anddefaultTagscarryingProject/Stage/ManagedBy: "sst". - Stage-gated lifecycle:
removal: stage === "prod" ? "retain" : "remove"
andprotect: stage === "prod"so prod resources survive a stack tear-down
and non-prod previews clean up. - SSM Parameter Store as the out-of-graph contract under a
/{app}/{stage}/{domain}/...prefix - for consumers that aren't in the
Pulumi graph (CI scripts, sibling apps, operators). For same-app Lambdas,
prefer SSTlink:(it wires a real dependency edge and grants IAM); don't
route same-app sharing through SSM. Seereferences/authoring.md§ Sharing. - Lazy
await import("./infra/<module>")insiderun()sosst dev
hot-reload stays light. (For testing, a module export still runs its top-levelnew sst.aws.*unless it's wrapped in a factory function - seereferences/testing.mdfor how to test infra.) - Source-level Vitest tests on every infra module - a lightweight,
house-style regression net asserting on the source text (resource names,
index shapes, IAM scopes). It's a deliberate choice, not an SST limit: Pulumi
does support runtime mocks (@pulumi/pulumi/runtime) for behavioral graph
tests when a module has real logic. Source assertions don't replace a
preview-deploy + smoke test. Seereferences/testing.md. - An observability gate: every new Lambda/queue/schedule gets an alarm and
structured logging before merge. Whether you enforce this depends on the
project, but it's cheap insurance. Seereferences/deploy-and-troubleshoot.md
§ Observability.
When you introduce a convention, say which bucket it's in ("this is universal"
vs "matching this repo's house style") so the user can override the
project-specific ones deliberately.
Working rhythm
- Orient (above) - map config, modules, tests, tooling.
- Verify syntax with Context7 / AWS docs MCP if anything is non-obvious.
Don't guess at a component's option name. - Author the resource/module following
references/authoring.md. Match the
surrounding file's commenting density and naming - these projects comment the
why heavily, and a terse one-liner in a heavily-annotated file reads as a
regression. - Test - add or update source-level assertions (
references/testing.md) and
runnpx vitest(or the repo'stestscript). Runnpx sst diffand/ortsc --noEmitto catch type and plan errors before deploying. - Deploy/operate per
references/deploy-and-troubleshoot.md. Confirm the
target account withaws sts get-caller-identitybefore anysst deploy. - Clean up any exported state files - they contain account IDs and ARNs and
must not linger in/tmpor chat history.
What good looks like
- The change is the smallest diff that satisfies the requirement, in the right
infra/module, wired intorun()in dependency order. - Every Lambda gets the right runtime via the global transform (you didn't
hand-setruntimeunless intentionally diverging - e.g. a Python function). - Cross-resource references use
link:(in-graph) and/or$interpolate-scoped
IAM; outputs other tools consume are published to SSM under the stage prefix. - New infra has a matching source-level test, and the existing suite stays green.
- You confirmed AWS-side facts via the docs MCP and SST/Pulumi syntax via
Context7 rather than relying on recall. - Anything irreversible (deploy,
sst remove, a resource-type migration) was
flagged to the user with the account it targets, and migrations were planned
as two PRs, not one.
Limitations
- Use this skill only when the task clearly matches its upstream source and local project context.
- Verify commands, generated code, dependencies, credentials, and external service behavior before applying changes.
- Do not treat examples as a substitute for environment-specific tests, security review, or user approval for destructive or costly actions.
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.