dist/plugins/infra-iac-sst/skills/infra-iac-sst/SKILL.md
SST (Ion) infrastructure-as-code — TypeScript-first serverless on AWS with Pulumi, resource linking, and live Lambda dev
npx skillsauth add agents-inc/skills infra-iac-sstInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: SST v3 (Ion) is TypeScript-first infrastructure-as-code for AWS, powered by Pulumi/Terraform (not CDK/CloudFormation). Define your entire app in
sst.config.tsusing high-level components (sst.aws.Function,sst.aws.ApiGatewayV2,sst.aws.Bucket,sst.aws.Dynamo, etc.). Use resource linking (link: [bucket]+Resource.MyBucket.name) for type-safe, permission-aware access between components. Usesst devfor live Lambda development with sub-10ms reloads. Use$app.stagefor multi-environment isolation. Usetransformto customize underlying Pulumi resources.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST use resource linking (link + Resource.*) to connect components — NEVER hardcode ARNs, table names, or bucket names)
(You MUST use $app.stage for environment isolation — NEVER share resources across stages without explicit intent)
(You MUST use sst dev for local development — it provides live Lambda proxying with sub-10ms reloads against real AWS resources)
(You MUST use sst secret set for secrets — NEVER put secrets in sst.config.ts, .env files committed to git, or environment variables)
(You MUST use transform to customize underlying resources — NEVER reach for raw Pulumi resources when an SST component exists)
</critical_requirements>
Auto-detection: SST, sst.config.ts, sst.aws.Function, sst.aws.ApiGatewayV2, sst.aws.Bucket, sst.aws.Dynamo, sst.aws.Queue, sst.aws.Topic, sst.aws.Cron, sst.aws.Nextjs, sst.aws.Remix, sst.aws.Astro, sst.aws.StaticSite, sst.aws.Vpc, sst.aws.Cluster, sst.aws.Postgres, sst.aws.Router, sst.Linkable, Resource from sst, sst dev, sst deploy, sst remove, sst secret, $app.stage, $transform, $concat, $interpolate, resource linking, live Lambda, Ion
When to use:
When NOT to use:
Key patterns covered:
sst.config.ts structure (app() + run() functions)link property + Resource.* SDKsst dev$app.stagesst secretsst.Linkable and Linkable.wrap$app, $dev, $concat, $interpolate, $resolve, $transformSST v3 (Ion) replaces CDK/CloudFormation with Pulumi/Terraform for dramatically faster deployments and a simpler programming model. The core ideas:
sst.config.ts. Infrastructure, frontends, and functions all declared together in TypeScript with loops, conditionals, and functions.sst.aws.* components encapsulate best practices (IAM, logging, monitoring). Use transform to reach into underlying resources when defaults aren't enough.link: [bucket] automatically grants IAM permissions and injects type-safe references. Access via Resource.MyBucket.name at runtime. No manual ARN passing or environment variable wiring.sst dev creates a personal stack). $app.stage drives resource naming. Production uses sst deploy --stage production.sst dev replaces Lambda functions with stubs that proxy to your local machine. Changes reload in under 10ms. No local emulation — your code runs against real DynamoDB, S3, SQS.When to use SST:
When NOT to use SST:
Every SST app has a single sst.config.ts with two functions: app() for metadata and run() for resources.
/// <reference path="./.sst/platform/config.d.ts" />
export default $config({
app(input) {
return {
name: "my-app",
home: "aws",
removal: input.stage === "production" ? "retain" : "remove",
protect: input.stage === "production",
providers: {
aws: { region: "us-east-1" },
},
};
},
async run() {
const bucket = new sst.aws.Bucket("Uploads");
const api = new sst.aws.Function("Api", {
handler: "src/api.handler",
link: [bucket],
});
return { apiUrl: api.url };
},
});
Why good: app() handles metadata and stage-specific policies, run() defines all resources, removal: "retain" protects production data, returned values become outputs in .sst/outputs.json
See examples/core.md for full config with multi-stage, providers, and protect patterns.
The defining SST feature. Link resources to grant permissions and type-safe access automatically.
// In sst.config.ts
const table = new sst.aws.Dynamo("Notes", {
fields: { userId: "string", noteId: "string" },
primaryIndex: { hashKey: "userId", rangeKey: "noteId" },
});
new sst.aws.Function("Api", {
handler: "src/api.handler",
link: [table],
});
// In src/api.ts — runtime code
import { Resource } from "sst";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
const client = new DynamoDBClient({});
// Resource.Notes.name is the table name — type-safe, auto-generated
console.log(Resource.Notes.name);
Why good: No manual ARN passing, IAM permissions granted automatically, type-safe access via generated sst-env.d.ts, works across Functions, frontends, and containers
See examples/core.md for linking patterns including custom linkables.
sst dev proxies Lambda invocations to your local machine for sub-10ms reload cycles.
# Start live dev — deploys infra, proxies Lambda to local machine
sst dev
# Opens multiplexer: deploys resources, starts frontends, tunnels VPC
// Detect dev mode in handler code
const isLocal = process.env.SST_DEV === "true";
Key behavior: Functions are replaced with stubs that forward events via AppSync to your local machine. Your local code runs as Node.js Workers. Changes reload instantly — no redeploy needed.
Gotcha: Killing sst dev leaves stubs deployed. Subsequent Lambda invocations will timeout until you run sst dev again or sst deploy to replace stubs with real code.
See examples/core.md for dev workflow and debugging setup.
SST provides high-level components for common AWS services. Each handles IAM, logging, and configuration automatically.
| Component | AWS Service | Use Case |
|-----------|-------------|----------|
| sst.aws.Function | Lambda | Serverless functions |
| sst.aws.ApiGatewayV2 | API Gateway v2 | HTTP APIs with routes |
| sst.aws.Dynamo | DynamoDB | NoSQL database |
| sst.aws.Bucket | S3 | Object storage |
| sst.aws.Queue | SQS | Message queues |
| sst.aws.Topic | SNS | Pub/sub messaging |
| sst.aws.Cron | EventBridge | Scheduled tasks |
| sst.aws.Vpc | VPC | Network isolation |
| sst.aws.Cluster | ECS | Container workloads |
| sst.aws.Postgres | RDS Postgres | Relational database |
| sst.aws.Nextjs | Lambda + CloudFront | Next.js deployment |
| sst.aws.Remix | Lambda + CloudFront | Remix deployment |
| sst.aws.Astro | Lambda + CloudFront | Astro deployment |
| sst.aws.StaticSite | S3 + CloudFront | Static site hosting |
| sst.aws.Router | CloudFront | URL routing |
See examples/api-data.md for API, database, storage, queue, and cron patterns.
Secrets are encrypted and stored in S3, injected into function bundles at deploy time.
# Set a secret (prompts for value)
sst secret set DATABASE_URL
# Set a secret with value inline
sst secret set STRIPE_KEY sk_live_xxx
# Load secrets from a file
sst secret load .env.production
# List all secrets
sst secret list
// Access secrets via resource linking
import { Resource } from "sst";
const stripeKey = Resource.StripeKey.value;
Gotcha: Secrets are per-stage. Set them for each stage separately. Use --fallback to set a default across all stages.
See examples/core.md for secrets with Linkable pattern.
Customize underlying Pulumi resources when SST defaults aren't enough.
// Per-component transform
new sst.aws.Function("Api", {
handler: "src/api.handler",
transform: {
function: (args) => {
args.tracingConfig = { mode: "Active" };
},
},
});
// Global transform — applies to ALL components of a type
$transform(sst.aws.Function, (args) => {
args.environment ??= {};
args.environment.variables ??= {};
args.environment.variables.STAGE = $app.stage;
});
Why good: Transforms let you customize any underlying resource property without abandoning SST's abstractions. Global transforms set defaults across all components.
See examples/deployment.md for transform patterns.
Every stage is a fully isolated deployment. Use $app.stage for conditional configuration.
async run() {
const isProd = $app.stage === "production";
const table = new sst.aws.Dynamo("Notes", {
fields: { userId: "string", noteId: "string" },
primaryIndex: { hashKey: "userId", rangeKey: "noteId" },
deletionProtection: isProd,
});
}
# Personal dev stage (default)
sst dev
# Deploy to staging
sst deploy --stage staging
# Deploy to production
sst deploy --stage production
See examples/deployment.md for multi-stage patterns with removal policies.
</patterns><decision_framework>
What are you building?
|
+-- HTTP API
| +-- Simple routes with Lambda handlers --> sst.aws.ApiGatewayV2
| +-- Need WebSocket support --> sst.aws.ApiGatewayWebSocket
| +-- URL routing / CDN --> sst.aws.Router
|
+-- Data storage
| +-- Key-value / document data --> sst.aws.Dynamo
| +-- Relational data with SQL --> sst.aws.Postgres
| +-- File/blob storage --> sst.aws.Bucket
|
+-- Async processing
| +-- Point-to-point messaging --> sst.aws.Queue (SQS)
| +-- Fan-out to multiple subscribers --> sst.aws.Topic (SNS)
| +-- Scheduled tasks --> sst.aws.Cron
|
+-- Compute
| +-- Serverless function --> sst.aws.Function
| +-- Container workload --> sst.aws.Cluster + sst.aws.Service
| +-- Long-running background job --> sst.aws.Function (up to 15min)
|
+-- Full-stack frontend
+-- Next.js --> sst.aws.Nextjs
+-- Remix --> sst.aws.Remix
+-- Astro --> sst.aws.Astro
+-- SvelteKit --> sst.aws.SvelteKit
+-- SolidStart --> sst.aws.SolidStart
+-- Static HTML/JS --> sst.aws.StaticSite
Need to set a property on an SST component?
|
+-- Property exists on the SST component args --> Use the SST property directly
|
+-- Property exists only on the underlying AWS resource --> Use transform
|
+-- Need to set a default across ALL instances of a component --> Use $transform()
|
+-- No SST component exists for this AWS service --> Use raw Pulumi resource
+-- Need to link it? --> Use sst.Linkable.wrap() or new sst.Linkable()
</decision_framework>
<red_flags>
High Priority Issues:
link + Resource.*) — defeats SST's type-safe wiring and breaks across stages$app.stage prefix — causes resource conflicts and accidental cross-stage accesssst.config.ts or committed .env files instead of using sst secret set — secrets leak to version controlsst dev for shared environments (staging, production) — stubs proxy to a single developer's machine, breaking for everyone elsesst.aws.* component exists — loses SST's linking, permissions, and defaultsMedium Priority Issues:
removal: "retain" and protect: true for production stages — accidental sst remove deletes all data/// <reference path="./.sst/platform/config.d.ts" /> at top of sst.config.ts — loses type checking for $app, $transform, etc..env files for secrets instead of sst secret — .env files aren't encrypted and must be managed manually per stagesst deploy after finishing sst dev session — stubs remain deployed and Lambda invocations timeoutCommon Mistakes:
sst dev stubs persist after you stop the process — always redeploy or re-run sst dev$app.stage in runtime code — it's only available in sst.config.ts, use Resource.* or env vars for runtime stage awarenesssst dev to emulate AWS locally — it doesn't; it proxies to real AWS resources in your accountGotchas & Edge Cases:
$concat() or $interpolate instead of template literalssst dev multiplexer starts frontends automatically — you don't need to run next dev or vite dev separately.fifo suffix in names — SST handles this automatically but be aware when referencing externally.env and .env.<stage> files are loaded automatically — .env takes precedence over stage-specific filesResource.*sst dev — local execution skips layersremoval setting in app() controls what happens when you run sst remove — "remove" deletes resources, "retain" keeps them, "retain-all" keeps everything including logs</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST use resource linking (link + Resource.*) to connect components — NEVER hardcode ARNs, table names, or bucket names)
(You MUST use $app.stage for environment isolation — NEVER share resources across stages without explicit intent)
(You MUST use sst dev for local development — it provides live Lambda proxying with sub-10ms reloads against real AWS resources)
(You MUST use sst secret set for secrets — NEVER put secrets in sst.config.ts, .env files committed to git, or environment variables)
(You MUST use transform to customize underlying resources — NEVER reach for raw Pulumi resources when an SST component exists)
Failure to follow these rules will cause cross-stage resource conflicts, leaked secrets, broken type safety, and unnecessary infrastructure complexity.
</critical_reminders>
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Mapbox GL JS interactive maps - map initialization, markers, popups, sources, layers, expressions, clustering, 3D terrain, geocoding, directions
tools
Leaflet interactive maps - map setup, tile layers, markers, popups, GeoJSON, custom controls, plugins, clustering, events