# Examples

Below is a collection of example SST apps. These are available in the [`examples/`](https://github.com/anomalyco/sst/tree/dev/examples) directory of the repo.

The descriptions for these examples are generated using the comments in the `sst.config.ts` of the app.

#### [Contributing](https://sst.dev/docs/examples/\#contributing)

To contribute an example or to edit one, submit a PR to the [repo](https://github.com/anomalyco/sst).
Make sure to document the `sst.config.ts` in your example.

* * *

## [API Gateway auth](https://sst.dev/docs/examples/\#api-gateway-auth)

Enable IAM and JWT authorizers for API Gateway routes.

```
const api = new sst.aws.ApiGatewayV2("MyApi", {

domain: {

name: "api.ion.sst.sh",

path: "v1",

},

});

api.route("GET /", {

handler: "route.handler",

});

api.route("GET /foo", "route.handler", { auth: { iam: true } });

api.route("GET /bar", "route.handler", {

auth: {

jwt: {

issuer:

"https://cognito-idp.us-east-1.amazonaws.com/us-east-1_Rq4d8zILG",

audiences: ["user@example.com"],

},

},

});

api.route("$default", "route.handler");
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-apig-auth).

* * *

## [AWS API Gateway V1 streaming](https://sst.dev/docs/examples/\#aws-api-gateway-v1-streaming)

An example on how to enable streaming for API Gateway REST API routes.

```
api.route("GET /", {

handler: "index.handler",

streaming: true,

});
```

The handler uses the native `awslambda.streamifyResponse` and
`awslambda.HttpResponseStream.from` to stream responses through API Gateway.

```
export const handler = awslambda.streamifyResponse(

async (event, stream) => {

stream = awslambda.HttpResponseStream.from(stream, {

statusCode: 200,

headers: {

"Content-Type": "text/plain; charset=UTF-8",

"X-Content-Type-Options": "nosniff",

},

});

stream.write("Hello ");

await new Promise((resolve) => setTimeout(resolve, 3000));

stream.write("World");

stream.end();

},

);
```

```
const api = new sst.aws.ApiGatewayV1("MyApi");

api.route("GET /", {

handler: "index.handler",

streaming: true,

});

api.route("GET /hono", {

handler: "hono.handler",

streaming: true,

});

api.deploy();
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-apigv1-stream).

* * *

## [AWS Astro container with Redis](https://sst.dev/docs/examples/\#aws-astro-container-with-redis)

Creates a hit counter app with Astro and Redis.

This deploys Astro as a Fargate service to ECS and it’s linked to Redis.

```
new sst.aws.Service("MyService", {

cluster,

link: [redis],

loadBalancer: {

ports: [{ listen: "80/http", forward: "3000/http" }],

},

dev: {

command: "npm run dev",

},

});
```

Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local
machine.

```
sudo npx sst tunnel install
```

This needs _sudo_ to create a network interface on your machine. You’ll only need to do this
once on your machine.

To start your app locally run.

```
npx sst dev
```

Now if you go to `http://localhost:4321` you’ll see a counter update as you refresh the page.

Finally, you can deploy it by adding the `Dockerfile` that’s included in this example and
running `npx sst deploy --stage production`.

```
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });

const redis = new sst.aws.Redis("MyRedis", { vpc });

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

new sst.aws.Service("MyService", {

cluster,

link: [redis],

loadBalancer: {

ports: [{ listen: "80/http", forward: "4321/http" }],

},

dev: {

command: "npm run dev",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-astro-redis).

* * *

## [AWS Astro streaming](https://sst.dev/docs/examples/\#aws-astro-streaming)

Follows the [Astro Streaming](https://docs.astro.build/en/recipes/streaming-improve-page-performance/) guide to create an app that streams HTML.

The `responseMode` in the [`astro-sst`](https://www.npmjs.com/package/astro-sst) adapter
is set to enable streaming.

```
adapter: aws({

responseMode: "stream"

})
```

Now any components that return promises will be streamed.

```
---

import type { Character } from "./character";

const friends: Character[] = await new Promise((resolve) => setTimeout(() => {

setTimeout(() => {

resolve(

[\
\
        { name: "Patrick Star", image: "patrick.png" },\
\
        { name: "Sandy Cheeks", image: "sandy.png" },\
\
        { name: "Squidward Tentacles", image: "squidward.png" },\
\
        { name: "Mr. Krabs", image: "mr-krabs.png" },\
\
      ]

);

}, 3000);

}));

---

<div class="grid">

{friends.map((friend) => (

<div class="card">

<img class="img" src={friend.image} alt={friend.name} />

<p>{friend.name}</p>

</div>

))}

</div>
```

You should see the _friends_ section load after a 3 second delay.

Safari uses a [different heuristic](https://bugs.webkit.org/show_bug.cgi?id=252413) to
determine when to stream data. You need to render _enough_ initial HTML to trigger streaming.
This is typically only a problem for demo apps.

There’s nothing to configure for streaming in the `Astro` component.

```
new sst.aws.Astro("MyWeb");
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-astro-stream).

* * *

## [AWS Aurora local](https://sst.dev/docs/examples/\#aws-aurora-local)

In this example, we connect to a locally running Postgres instance for dev. While
on deploy, we use RDS Aurora.

We use the [`docker run`](https://docs.docker.com/reference/cli/docker/container/run/) CLI
to start a local container with Postgres. You don’t have to use Docker, you can use
Postgres.app or any other way to run Postgres locally.

```
docker run \

--rm \

-p 5432:5432 \

-v $(pwd)/.sst/storage/postgres:/var/lib/postgresql/data \

-e POSTGRES_USER=postgres \

-e POSTGRES_PASSWORD=password \

-e POSTGRES_DB=local \

postgres:16.4
```

The data is saved to the `.sst/storage` directory. So if you restart the dev server, the
data will still be there.

We then configure the `dev` property of the `Aurora` component with the settings for the
local Postgres instance.

```
dev: {

username: "postgres",

password: "password",

database: "local",

port: 5432,

}
```

By providing the `dev` prop for Postgres, SST will use the local Postgres instance and
not deploy a new RDS database when running `sst dev`.

It also allows us to access the database through a Resource `link` without having to
conditionally check if we are running locally.

```
const pool = new Pool({

host: Resource.MyPostgres.host,

port: Resource.MyPostgres.port,

user: Resource.MyPostgres.username,

password: Resource.MyPostgres.password,

database: Resource.MyPostgres.database,

});
```

The above will work in both `sst dev` and `sst deploy`.

```
const vpc = new sst.aws.Vpc("MyVpc", { nat: "ec2" });

const database = new sst.aws.Aurora("MyPostgres", {

engine: "postgres",

dev: {

username: "postgres",

password: "password",

database: "local",

host: "localhost",

port: 5432,

},

vpc,

});

new sst.aws.Function("MyFunction", {

vpc,

url: true,

link: [database],

handler: "index.handler",

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-aurora-local).

* * *

## [AWS Aurora MySQL](https://sst.dev/docs/examples/\#aws-aurora-mysql)

In this example, we deploy a Aurora MySQL database.

```
const mysql = new sst.aws.Aurora("MyDatabase", {

engine: "mysql",

vpc,

});
```

And link it to a Lambda function.

```
new sst.aws.Function("MyApp", {

handler: "index.handler",

link: [mysql],

url: true,

vpc,

});
```

Now in the function we can access the database.

```
const connection = await mysql.createConnection({

database: Resource.MyDatabase.database,

host: Resource.MyDatabase.host,

port: Resource.MyDatabase.port,

user: Resource.MyDatabase.username,

password: Resource.MyDatabase.password,

});
```

We also enable the `bastion` option for the VPC. This allows us to connect to the database
from our local machine with the `sst tunnel` CLI.

```
sudo npx sst tunnel install
```

This needs _sudo_ to create a network interface on your machine. You’ll only need to do this
once on your machine.

Now you can run `npx sst dev` and you can connect to the database from your local machine.

```
const vpc = new sst.aws.Vpc("MyVpc", {

nat: "ec2",

bastion: true,

});

const mysql = new sst.aws.Aurora("MyDatabase", {

engine: "mysql",

vpc,

});

new sst.aws.Function("MyApp", {

handler: "index.handler",

link: [mysql],

url: true,

vpc,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-aurora-mysql).

* * *

## [AWS Aurora Postgres](https://sst.dev/docs/examples/\#aws-aurora-postgres)

In this example, we deploy a Aurora Postgres database.

```
const postgres = new sst.aws.Aurora("MyDatabase", {

engine: "postgres",

vpc,

});
```

And link it to a Lambda function.

```
new sst.aws.Function("MyApp", {

handler: "index.handler",

link: [postgres],

url: true,

vpc,

});
```

In the function we use the [`postgres`](https://www.npmjs.com/package/postgres) package.

```
import postgres from "postgres";

import { Resource } from "sst";

const sql = postgres({

username: Resource.MyDatabase.username,

password: Resource.MyDatabase.password,

database: Resource.MyDatabase.database,

host: Resource.MyDatabase.host,

port: Resource.MyDatabase.port,

});
```

We also enable the `bastion` option for the VPC. This allows us to connect to the database
from our local machine with the `sst tunnel` CLI.

```
sudo npx sst tunnel install
```

This needs _sudo_ to create a network interface on your machine. You’ll only need to do this
once on your machine.

Now you can run `npx sst dev` and you can connect to the database from your local machine.

```
const vpc = new sst.aws.Vpc("MyVpc", {

nat: "ec2",

bastion: true,

});

const postgres = new sst.aws.Aurora("MyDatabase", {

engine: "postgres",

vpc,

});

new sst.aws.Function("MyApp", {

handler: "index.handler",

link: [postgres],

url: true,

vpc,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-aurora-postgres).

* * *

## [AWS OpenAuth React SPA](https://sst.dev/docs/examples/\#aws-openauth-react-spa)

This is a full-stack monorepo app shows the OpenAuth flow for a single-page app
and an authenticated API. It has:

- React SPA built with Vite and the `StaticSite` component in the `packages/web`
directory.

```
export const web = new sst.aws.StaticSite("MyWeb", {

path: "packages/web",

build: {

output: "dist",

command: "npm run build",

},

environment: {

VITE_API_URL: api.url,

VITE_AUTH_URL: auth.url,

},

});
```

- API with Hono and the `Function` component in `packages/functions/src/api.ts`.

```
export const api = new sst.aws.Function("MyApi", {

url: true,

link: [auth],

handler: "packages/functions/src/api.handler",

});
```

- OpenAuth with the `Auth` component in `packages/functions/src/auth.ts`.

```
export const auth = new sst.aws.Auth("MyAuth", {

issuer: "packages/functions/src/auth.handler",

});
```

The React frontend uses a `AuthContext` provider to manage the auth flow.

```
<AuthContext.Provider

value={{

login,

logout,

userId,

loaded,

loggedIn,

getToken,

}}

>

{children}

</AuthContext.Provider>
```

Now in `App.tsx`, we can use the `useAuth` hook.

```
const auth = useAuth();

return !auth.loaded ? (

<div>Loading...</div>

) : (

<div>

{auth.loggedIn ? (

<div>

<p>

<span>Logged in</span>

{auth.userId && <span> as {auth.userId}</span>}

</p>

</div>

) : (

<button onClick={auth.login}>Login with OAuth</button>

)}

</div>

);
```

Once authenticated, we can call our authenticated API by passing in the access
token.

```
await fetch(`${import.meta.env.VITE_API_URL}me`, {

headers: {

Authorization: `Bearer ${await auth.getToken()}`,

},

});
```

The API uses the OpenAuth client to verify the token.

```
const authHeader = c.req.header("Authorization");

const token = authHeader.split(" ")[1];

const verified = await client.verify(subjects, token);
```

The `sst.config.ts` dynamically imports all the `infra/` files.

```
await import("./infra/auth");

await import("./infra/api");

await import("./infra/web");
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-auth-react).

* * *

## [Bucket lifecycle policies](https://sst.dev/docs/examples/\#bucket-lifecycle-policies)

Configure S3 bucket lifecycle policies to expire objects automatically.

```
const bucket = new sst.aws.Bucket("MyBucket", {

lifecycle: [\
\
    {\
\
      expiresIn: "60 days",\
\
    },\
\
    {\
\
      id: "expire-tmp-files",\
\
      prefix: "tmp/",\
\
      expiresIn: "30 days",\
\
    },\
\
    {\
\
      prefix: "data/",\
\
      expiresAt: "2028-12-31",\
\
    },\
\
  ],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-bucket-lifecycle-rules).

* * *

## [Bucket policy](https://sst.dev/docs/examples/\#bucket-policy)

Create an S3 bucket and transform its bucket policy.

```
const bucket = new sst.aws.Bucket("MyBucket", {

transform: {

policy: (args) => {

// use sst.aws.iamEdit helper function to manipulate IAM policy

// containing Output values from components

args.policy = sst.aws.iamEdit(args.policy, (policy) => {

policy.Statement.push({

Effect: "Allow",

Principal: { Service: "ses.amazonaws.com" },

Action: "s3:PutObject",

Resource: $interpolate`arn:aws:s3:::${args.bucket}/*`,

});

});

},

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-bucket-policy).

* * *

## [Bucket queue notifications](https://sst.dev/docs/examples/\#bucket-queue-notifications)

Create an S3 bucket and subscribe to its events with an SQS queue.

```
const queue = new sst.aws.Queue("MyQueue");

queue.subscribe("subscriber.handler");

const bucket = new sst.aws.Bucket("MyBucket");

bucket.notify({

notifications: [\
\
    {\
\
      name: "MySubscriber",\
\
      queue,\
\
      events: ["s3:ObjectCreated:*"],\
\
    },\
\
  ],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-bucket-queue-subscriber).

* * *

## [Bucket notifications](https://sst.dev/docs/examples/\#bucket-notifications)

Create an S3 bucket and subscribe to its events with a function.

```
const bucket = new sst.aws.Bucket("MyBucket");

bucket.notify({

notifications: [\
\
    {\
\
      name: "MySubscriber",\
\
      function: "subscriber.handler",\
\
      events: ["s3:ObjectCreated:*"],\
\
    },\
\
  ],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-bucket-subscriber).

* * *

## [Bucket topic notifications](https://sst.dev/docs/examples/\#bucket-topic-notifications)

Create an S3 bucket and subscribe to its events with an SNS topic.

```
const topic = new sst.aws.SnsTopic("MyTopic");

topic.subscribe("MySubscriber", "subscriber.handler");

const bucket = new sst.aws.Bucket("MyBucket");

bucket.notify({

notifications: [\
\
    {\
\
      name: "MySubscriber",\
\
      topic,\
\
      events: ["s3:ObjectCreated:*"],\
\
    },\
\
  ],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-bucket-topic-subscriber).

* * *

## [AWS Bun Elysia container](https://sst.dev/docs/examples/\#aws-bun-elysia-container)

Deploys a Bun [Elysia](https://elysiajs.com/) API to AWS.

You can get started by running.

```
bun create elysia aws-bun-elysia

cd aws-bun-elysia

bunx sst init
```

Now you can add a service.

```
new sst.aws.Service("MyService", {

cluster,

loadBalancer: {

ports: [{ listen: "80/http", forward: "3000/http" }],

},

dev: {

command: "bun dev",

},

});
```

Start your app locally.

```
bun sst dev
```

This example lets you upload a file to S3 and then download it.

```
curl -F file=@elysia.png http://localhost:3000/

curl http://localhost:3000/latest
```

Finally, you can deploy it using `bun sst deploy --stage production`.

```
const bucket = new sst.aws.Bucket("MyBucket");

const vpc = new sst.aws.Vpc("MyVpc");

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

new sst.aws.Service("MyService", {

cluster,

loadBalancer: {

ports: [{ listen: "80/http", forward: "3000/http" }],

},

dev: {

command: "bun dev",

},

link: [bucket],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-bun-elysia).

* * *

## [AWS Bun Redis](https://sst.dev/docs/examples/\#aws-bun-redis)

Creates a hit counter app with Bun and Redis.

This deploys Bun as a Fargate service to ECS and it’s linked to Redis.

```
new sst.aws.Service("MyService", {

cluster,

loadBalancer: {

ports: [{ listen: "80/http", forward: "3000/http" }],

},

dev: {

command: "bun dev",

},

link: [redis],

});
```

We also have a couple of scripts. A `dev` script with a watcher and a `build` script
that used when we deploy to production.

```
{

"scripts": {

"dev": "bun run --watch index.ts",

"build": "bun build --target bun index.ts"

},

}
```

Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local
machine.

```
sudo bun sst tunnel install
```

This needs _sudo_ to create a network interface on your machine. You’ll only need to do this
once on your machine.

To start your app locally run.

```
bun sst dev
```

Now if you go to `http://localhost:3000` you’ll see a counter update as you refresh the page.

Finally, you can deploy it using `bun sst deploy --stage production` using a `Dockerfile`
that’s included in the example.

```
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });

const redis = new sst.aws.Redis("MyRedis", { vpc });

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

new sst.aws.Service("MyService", {

cluster,

link: [redis],

loadBalancer: {

ports: [{ listen: "80/http", forward: "3000/http" }],

},

dev: {

command: "bun dev",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-bun-redis).

* * *

## [AWS Bus subscriptions](https://sst.dev/docs/examples/\#aws-bus-subscriptions)

Subscribe bus events with AWS Lambda functions.

```
const bus = new sst.aws.Bus("Bus");

const publisher = new sst.aws.Function("Publisher", {

handler: "./src/publisher.handler",

url: true,

link: [bus],

});

bus.subscribe("Example", "./src/receiver.handler");
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-bus).

* * *

## [AWS Cluster custom autoscaling](https://sst.dev/docs/examples/\#aws-cluster-custom-autoscaling)

In this example, we’ll create a cluster that autoscales based on a custom
metric. In this case, the number of messages in a queue.

We’ll create a queue, and two functions that’ll seed and purge the queue. We’ll
also create two policies.

One that scales it up.

```
const scaleUpPolicy = new aws.appautoscaling.Policy("ScaleUpPolicy", {

serviceNamespace: service.nodes.autoScalingTarget.serviceNamespace,

scalableDimension: service.nodes.autoScalingTarget.scalableDimension,

resourceId: service.nodes.autoScalingTarget.resourceId,

policyType: "StepScaling",

stepScalingPolicyConfiguration: {

adjustmentType: "ChangeInCapacity",

cooldown: 5,

stepAdjustments: [\
\
      {\
\
        metricIntervalLowerBound: "0",\
\
        scalingAdjustment: 1,\
\
      },\
\
    ],

},

});
```

And one that scales it down.

```
const scaleDownPolicy = new aws.appautoscaling.Policy("ScaleDownPolicy", {

serviceNamespace: service.nodes.autoScalingTarget.serviceNamespace,

scalableDimension: service.nodes.autoScalingTarget.scalableDimension,

resourceId: service.nodes.autoScalingTarget.resourceId,

policyType: "StepScaling",

stepScalingPolicyConfiguration: {

adjustmentType: "ChangeInCapacity",

cooldown: 5,

stepAdjustments: [\
\
      {\
\
        metricIntervalUpperBound: "0",\
\
        scalingAdjustment: -1,\
\
      },\
\
    ],

},

});
```

We’ll add a CloudWatch metric alarm that triggers the scaling policies if the
queue depth exceeds 3 messages.

```
new aws.cloudwatch.MetricAlarm("QueueDepthAlarm", {

comparisonOperator: "GreaterThanThreshold",

evaluationPeriods: 1,

metricName: "ApproximateNumberOfMessagesVisible",

namespace: "AWS/SQS",

period: 10,

statistic: "Average",

threshold: 3,

dimensions: {

QueueName: queue.nodes.queue.name,

},

alarmDescription: "Scale up when queue depth exceeds 3 messages",

alarmActions: [scaleUpPolicy.arn],

okActions: [scaleDownPolicy.arn],

});
```

To test this example, first deploy your app then:

1. Invoke the `MyQueueSeeder` URL. This will cause the service to scale up to 5
instances in a few minutes.
2. Then invoke the `MyQueuePurger` URL. This will cause the service to scale
down to 1 instance in a few minutes.

```
const vpc = new sst.aws.Vpc("MyVpc");

// Create a queue and two functions to seed and purge the queue

const queue = new sst.aws.Queue("MyQueue");

new sst.aws.Function("MyQueueSeeder", {

handler: "queue.seeder",

link: [queue],

url: true,

});

new sst.aws.Function("MyQueuePurger", {

handler: "queue.purger",

link: [queue],

url: true,

});

// Create a cluster and disable default scaling on CPU and memory utilization

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

const service = new sst.aws.Service("MyService", {

cluster,

loadBalancer: {

ports: [{ listen: "80/http" }],

},

scaling: {

min: 1,

max: 5,

cpuUtilization: false,

memoryUtilization: false,

},

});

// Create a scale up policy that scales up by 1 instance at a time

const scaleUpPolicy = new aws.appautoscaling.Policy("ScaleUpPolicy", {

serviceNamespace: service.nodes.autoScalingTarget.serviceNamespace,

scalableDimension: service.nodes.autoScalingTarget.scalableDimension,

resourceId: service.nodes.autoScalingTarget.resourceId,

policyType: "StepScaling",

stepScalingPolicyConfiguration: {

adjustmentType: "ChangeInCapacity",

cooldown: 5,

stepAdjustments: [\
\
      {\
\
        metricIntervalLowerBound: "0",\
\
        scalingAdjustment: 1,\
\
      },\
\
    ],

},

});

// Create a scale down policy that scales down by 1 instance at a time

const scaleDownPolicy = new aws.appautoscaling.Policy("ScaleDownPolicy", {

serviceNamespace: service.nodes.autoScalingTarget.serviceNamespace,

scalableDimension: service.nodes.autoScalingTarget.scalableDimension,

resourceId: service.nodes.autoScalingTarget.resourceId,

policyType: "StepScaling",

stepScalingPolicyConfiguration: {

adjustmentType: "ChangeInCapacity",

cooldown: 5,

stepAdjustments: [\
\
      {\
\
        metricIntervalUpperBound: "0",\
\
        scalingAdjustment: -1,\
\
      },\
\
    ],

},

});

// Create an alarm that scales up when the queue depth exceeds 3 messages

// and scales down when the queue depth is less than 3 messages

new aws.cloudwatch.MetricAlarm("QueueDepthAlarm", {

comparisonOperator: "GreaterThanThreshold",

evaluationPeriods: 1,

metricName: "ApproximateNumberOfMessagesVisible",

namespace: "AWS/SQS",

period: 10,

statistic: "Average",

threshold: 3,

dimensions: {

QueueName: queue.nodes.queue.name,

},

alarmDescription: "Scale up when queue depth exceeds 3 messages",

alarmActions: [scaleUpPolicy.arn],

okActions: [scaleDownPolicy.arn],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-cluster-autoscaling).

* * *

## [AWS Cluster private service](https://sst.dev/docs/examples/\#aws-cluster-private-service)

Adds a private load balancer to a service by setting the `loadBalancer.public` prop to
`false`.

This allows you to create internal services that can only be accessed inside a VPC.

```
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

new sst.aws.Service("MyService", {

cluster,

loadBalancer: {

public: false,

ports: [{ listen: "80/http" }],

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-cluster-internal).

* * *

## [AWS Cluster Spot capacity](https://sst.dev/docs/examples/\#aws-cluster-spot-capacity)

This example, shows how to use the Fargate Spot capacity provider for your services.

We have it set to use only Fargate Spot instances for all non-production stages. Learn more
about the [`capacity`](https://sst.dev/docs/component/aws/cluster#capacity) prop.

```
const vpc = new sst.aws.Vpc("MyVpc");

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

new sst.aws.Service("MyService", {

cluster,

loadBalancer: {

ports: [{ listen: "80/http" }],

},

capacity: $app.stage === "production" ? undefined : "spot",

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-cluster-spot).

* * *

## [AWS Cluster with API Gateway](https://sst.dev/docs/examples/\#aws-cluster-with-api-gateway)

Expose a service through API Gateway HTTP API using a VPC link.

This is an alternative to using a load balancer. Since API Gateway is pay per request, it
works out a lot cheaper for services that don’t get a lot of traffic.

You need to specify which port in your service will be exposed through API Gateway.

```
const service = new sst.aws.Service("MyService", {

cluster,

serviceRegistry: {

port: 80,

},

});
```

A couple of things to note:

1. Your API Gateway HTTP API also needs to be in the **same VPC** as the service.

2. You also need to verify that your VPC’s [**availability zones support VPC link**](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vpc-links.html#http-api-vpc-link-availability).

3. Run `aws ec2 describe-availability-zones` to get a list of AZs for your
account.

4. Only list the AZ ID’s that support VPC link.

```
vpc: {

az: ["eu-west-3a", "eu-west-3c"]

}
```

```
   If the VPC picks an AZ automatically that doesn't support VPC link, you'll get

the following error:
```

operation error ApiGatewayV2: BadRequestException: Subnet is in Availability
Zone ‘euw3-az2’ where service is not available

````
```ts title="sst.config.ts"

const vpc = new sst.aws.Vpc("MyVpc", {

// Pick at least two AZs that support VPC link

// az: ["eu-west-3a", "eu-west-3c"],

});

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

const service = new sst.aws.Service("MyService", {

cluster,

serviceRegistry: {

port: 80,

},

});

const api = new sst.aws.ApiGatewayV2("MyApi", { vpc });

api.routePrivate("$default", service.nodes.cloudmapService.arn);
````

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-cluster-vpclink).

* * *

## [AWS Cognito User Pool](https://sst.dev/docs/examples/\#aws-cognito-user-pool)

Create a Cognito User Pool with a hosted UI domain, client, and identity pool.

```
const userPool = new sst.aws.CognitoUserPool("MyUserPool", {

domain: {

prefix: `my-app-${$app.stage}`,

},

triggers: {

preSignUp: {

handler: "index.handler",

},

},

});

const client = userPool.addClient("Web", {

callbackUrls: ['https://example.com/auth/callback']

});

const identityPool = new sst.aws.CognitoIdentityPool("MyIdentityPool", {

userPools: [\
\
    {\
\
      userPool: userPool.id,\
\
      client: client.id,\
\
    },\
\
],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-cognito).

* * *

## [Subscribe to queues with dead-letter queue](https://sst.dev/docs/examples/\#subscribe-to-queues-with-dead-letter-queue)

Messages not processed successfully by the primary subscriber function will be sent to the dead-letter queue after the retry limit is reached.

```
// create dead letter queue

const dlq = new sst.aws.Queue("DeadLetterQueue");

dlq.subscribe("subscriber.dlq");

// create main queue

const queue = new sst.aws.Queue("MyQueue", {

dlq: dlq.arn,

});

queue.subscribe("subscriber.main");

const app = new sst.aws.Function("MyApp", {

handler: "publisher.handler",

link: [queue],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-dead-letter-queue).

* * *

## [AWS Deno Redis](https://sst.dev/docs/examples/\#aws-deno-redis)

Creates a hit counter app with Deno and Redis.

This deploys Deno as a Fargate service to ECS and it’s linked to Redis.

```
new sst.aws.Service("MyService", {

cluster,

link: [redis],

loadBalancer: {

ports: [{ listen: "80/http", forward: "8000/http" }],

},

dev: {

command: "deno task dev",

},

});
```

Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local
machine.

```
sudo sst tunnel install
```

This needs _sudo_ to create a network interface on your machine. You’ll only need to do this
once on your machine.

To start your app locally run.

```
sst dev
```

Now if you go to `http://localhost:8000` you’ll see a counter update as you refresh the page.

Finally, you can deploy it using `sst deploy --stage production` using a `Dockerfile`
that’s included in the example.

```
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });

const redis = new sst.aws.Redis("MyRedis", { vpc });

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

new sst.aws.Service("MyService", {

cluster,

link: [redis],

loadBalancer: {

ports: [{ listen: "80/http", forward: "8000/http" }],

},

dev: {

command: "deno task dev",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-deno-redis).

* * *

## [Drizzle migrations in CI/CD](https://sst.dev/docs/examples/\#drizzle-migrations-in-cicd)

An example on how to run Drizzle migrations as a part of your CI/CD.

Start by creating a function that runs migrations.

```
const migrator = new sst.aws.Function("DatabaseMigrator", {

handler: "src/migrator.handler",

link: [rds],

vpc,

copyFiles: [\
\
    {\
\
      from: "migrations",\
\
      to: "./migrations",\
\
    },\
\
],

});
```

Where `src/migrator.ts` looks like.

```
import { db } from "./drizzle";

import { migrate } from "drizzle-orm/postgres-js/migrator";

export const handler = async (event: any) => {

await migrate(db, {

migrationsFolder: "./migrations",

});

};
```

And we can set it up to run on every deploy.

```
if (!$dev){

new aws.lambda.Invocation("DatabaseMigratorInvocation", {

input: Date.now().toString(),

functionName: migrator.name,

});

}
```

We use the current time to make sure the function runs on every deploy.

```
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true, nat: "ec2" });

const rds = new sst.aws.Postgres("MyPostgres", { vpc, proxy: true });

new sst.aws.Function("MyApi", {

vpc,

url: true,

link: [rds],

handler: "src/api.handler",

});

const migrator = new sst.aws.Function("DatabaseMigrator", {

handler: "src/migrator.handler",

link: [rds],

vpc,

copyFiles: [\
\
    {\
\
      from: "migrations",\
\
      to: "./migrations",\
\
    },\
\
],

});

if (!$dev) {

new aws.lambda.Invocation("DatabaseMigratorInvocation", {

input: Date.now().toString(),

functionName: migrator.name,

});

}

new sst.x.DevCommand("Studio", {

link: [rds],

dev: {

command: "npx drizzle-kit studio",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-drizzle-migrations).

* * *

## [AWS Aurora DSQL with Drizzle](https://sst.dev/docs/examples/\#aws-aurora-dsql-with-drizzle)

In this example, we use Drizzle ORM with an Aurora DSQL cluster.

```
const cluster = new sst.aws.Dsql("MyCluster");
```

And link it to a Lambda function.

```
new sst.aws.Function("MyApi", {

handler: "src/api.handler",

link: [cluster],

url: true,

});
```

Push the Drizzle schema to the database.

```
sst shell -- bun run push.ts
```

Now in the function we can connect to the cluster using Drizzle with the DSQL connector.
Learn more about [DSQL Node.js connectors](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/SECTION_Node-js-connectors.html).

```
import { drizzle } from "drizzle-orm/node-postgres";

import { AuroraDSQLPool } from "@aws/aurora-dsql-node-postgres-connector";

import { Resource } from "sst";

const pool = new AuroraDSQLPool({

host: Resource.MyCluster.endpoint,

user: "admin",

});

export const db = drizzle(pool, { schema });
```

```
const cluster = new sst.aws.Dsql("MyCluster");

new sst.aws.Function("MyApi", {

handler: "src/api.handler",

link: [cluster],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-dsql-drizzle).

* * *

## [AWS Aurora DSQL Multi-Region](https://sst.dev/docs/examples/\#aws-aurora-dsql-multi-region)

In this example, we deploy a multi-region Aurora DSQL cluster and connect to both
clusters from a Lambda function.

Create the cluster with a witness region and a peer region. The witness must differ
from both cluster regions.

```
const cluster = new sst.aws.Dsql("MultiRegion", {

regions: {

witness: "us-west-2",

peer: "us-east-2",

},

});
```

Connect to both clusters from your function using the DSQL connector.
Learn more about [DSQL Node.js connectors](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/SECTION_Node-js-connectors.html).

```
import { AuroraDSQLClient } from "@aws/aurora-dsql-node-postgres-connector";

import { Resource } from "sst";

async function connectToCluster(endpoint: string) {

const client = new AuroraDSQLClient({ host: endpoint, user: "admin" });

await client.connect();

return client;

}

// Cluster in us-east-1

const usEast1 = await connectToCluster(Resource.MultiRegion.endpoint);

// Cluster in us-east-2

const usEast2 = await connectToCluster(Resource.MultiRegion.peer.endpoint);
```

```
const cluster = new sst.aws.Dsql("MultiRegion", {

backup: true,

regions: {

witness: "us-west-2",

peer: "us-east-2",

},

});

const fn = new sst.aws.Function("MyFunction", {

handler: "lambda.handler",

link: [cluster],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-dsql-multiregion).

* * *

## [AWS Aurora DSQL in a VPC](https://sst.dev/docs/examples/\#aws-aurora-dsql-in-a-vpc)

In this example, we connect to an Aurora DSQL cluster privately from a Lambda
function using VPC endpoints, without routing traffic over the public internet.

Create a VPC, then create the cluster with a connection endpoint inside it.

```
const vpc = new sst.aws.Vpc("MyVpc");

const cluster = new sst.aws.Dsql("MyCluster", {

vpc: {

instance: vpc,

endpoints: { connection: true },

},

});
```

Link the cluster to a function that’s also in the VPC. The linked `endpoint` will
automatically resolve to the private VPC endpoint hostname instead of the public one.

```
new sst.aws.Function("MyFunction", {

handler: "lambda.handler",

vpc,

link: [cluster],

});
```

Connect from your function using the DSQL connector — no config changes needed.
Learn more about [DSQL Node.js connectors](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/SECTION_Node-js-connectors.html).

```
import { AuroraDSQLClient } from "@aws/aurora-dsql-node-postgres-connector";

import { Resource } from "sst";

const client = new AuroraDSQLClient({

host: Resource.MyCluster.endpoint,

user: "admin",

});

await client.connect();

const result = await client.query("SELECT NOW()");

await client.end();
```

```
const vpc = new sst.aws.Vpc("singleClusterVpc");

const cluster = new sst.aws.Dsql("MyCluster", {

vpc: {

instance: vpc,

endpoints: {

connection: true,

management: false,

},

},

});

const fn = new sst.aws.Function("MyFunction", {

handler: "lambda.handler",

vpc,

link: [cluster],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-dsql-vpc).

* * *

## [AWS Aurora DSQL](https://sst.dev/docs/examples/\#aws-aurora-dsql)

In this example, we deploy an Aurora DSQL cluster.

```
const cluster = new sst.aws.Dsql("MyCluster");
```

And link it to a Lambda function.

```
new sst.aws.Function("MyFunction", {

handler: "lambda.handler",

link: [cluster],

url: true,

});
```

Now in the function we can connect to the cluster using the DSQL connector.
Learn more about [DSQL Node.js connectors](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/SECTION_Node-js-connectors.html).

```
import { AuroraDSQLClient } from "@aws/aurora-dsql-node-postgres-connector";

import { Resource } from "sst";

const client = new AuroraDSQLClient({

host: Resource.MyCluster.endpoint,

user: "admin",

});

await client.connect();

const result = await client.query("SELECT NOW()");

await client.end();
```

```
const cluster = new sst.aws.Dsql("MyCluster", {});

const fn = new sst.aws.Function("MyFunction", {

handler: "lambda.handler",

link: [cluster],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-dsql).

* * *

## [DynamoDB composite keys](https://sst.dev/docs/examples/\#dynamodb-composite-keys)

Create a DynamoDB table with multi-attribute composite keys in a global secondary index.

```
const table = new sst.aws.Dynamo("MyTable", {

fields: {

userId: "string",

noteId: "string",

region: "string",

category: "string",

createdAt: "number",

},

primaryIndex: { hashKey: "userId", rangeKey: "noteId" },

globalIndexes: {

RegionCategoryIndex: {

hashKey: ["region", "category"],

rangeKey: "createdAt",

},

},

});

const creator = new sst.aws.Function("MyCreator", {

handler: "creator.handler",

link: [table],

url: true,

});

const reader = new sst.aws.Function("MyReader", {

handler: "reader.handler",

link: [table],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-dynamo-composite-keys).

* * *

## [DynamoDB streams](https://sst.dev/docs/examples/\#dynamodb-streams)

Create a DynamoDB table, enable streams, and subscribe to it with a function.

```
const table = new sst.aws.Dynamo("MyTable", {

fields: {

id: "string",

},

primaryIndex: { hashKey: "id" },

stream: "new-and-old-images",

});

table.subscribe("MySubscriber", "subscriber.handler", {

filters: [\
\
    {\
\
      dynamodb: {\
\
        NewImage: {\
\
          message: {\
\
            S: ["Hello"],\
\
          },\
\
        },\
\
      },\
\
    },\
\
],

});

const creator = new sst.aws.Function("MyCreator", {

handler: "creator.handler",

link: [table],

url: true,

});

const reader = new sst.aws.Function("MyReader", {

handler: "reader.handler",

link: [table],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-dynamo).

* * *

## [EC2 with Pulumi](https://sst.dev/docs/examples/\#ec2-with-pulumi)

Use raw Pulumi resources to create an EC2 instance.

```
// Notice you don't need to import pulumi, it is already part of sst.

const securityGroup = new aws.ec2.SecurityGroup("web-secgrp", {

ingress: [\
\
    {\
\
      protocol: "tcp",\
\
      fromPort: 80,\
\
      toPort: 80,\
\
      cidrBlocks: ["0.0.0.0/0"],\
\
    },\
\
],

});

// Find the latest Ubuntu AMI

const ami = aws.ec2.getAmi({

filters: [\
\
    {\
\
      name: "name",\
\
      values: ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"],\
\
    },\
\
],

mostRecent: true,

owners: ["099720109477"], // Canonical

});

// User data to set up a simple web server

const userData = `#!/bin/bash

ho "Hello, World!" > index.html

hup python3 -m http.server 80 &`;

// Create an EC2 instance

const server = new aws.ec2.Instance("web-server", {

instanceType: "t2.micro",

ami: ami.then((ami) => ami.id),

userData: userData,

vpcSecurityGroupIds: [securityGroup.id],

associatePublicIpAddress: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-ec2-pulumi).

* * *

## [AWS EFS with SQLite](https://sst.dev/docs/examples/\#aws-efs-with-sqlite)

Mount an EFS file system to a function and write to a SQLite database.

```
const db = sqlite3("/mnt/efs/mydb.sqlite");
```

The file system is mounted to `/mnt/efs` in the function.

This example is for demonstration purposes only. It’s not recommended to use
EFS for databases in production.

```
// NAT Gateways are required for Lambda functions

const vpc = new sst.aws.Vpc("MyVpc", { nat: "managed" });

// Create an EFS file system to store the SQLite database

const efs = new sst.aws.Efs("MyEfs", { vpc });

// Create a Lambda function that queries the database

new sst.aws.Function("MyFunction", {

vpc,

url: true,

volume: {

efs,

path: "/mnt/efs",

},

handler: "index.handler",

nodejs: {

install: ["better-sqlite3"],

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-efs-sqlite).

* * *

## [AWS EFS with SurrealDB](https://sst.dev/docs/examples/\#aws-efs-with-surrealdb)

We use the SurrealDB docker image to run a server in a container and use EFS as the file
system.

```
const server = new sst.aws.Service("MyService", {

cluster,

architecture: "arm64",

image: "surrealdb/surrealdb:v2.0.2",

// ...

volumes: [\
\
    { efs, path: "/data" },\
\
],

});
```

We then connect to the server from a Lambda function.

```
const endpoint = `http://${Resource.MyConfig.host}:${Resource.MyConfig.port}`;

const db = new Surreal();

await db.connect(endpoint);
```

This uses the SurrealDB client to connect to the server.

This example is for demonstration purposes only. It’s not recommended to use
EFS for databases in production.

```
const { RandomPassword } = await import("@pulumi/random");

// SurrealDB Credentials

const PORT = 8080;

const NAMESPACE = "test";

const DATABASE = "test";

const USERNAME = "root";

const PASSWORD = new RandomPassword("Password", {

length: 32,

}).result;

// NAT Gateways are required for Lambda functions

const vpc = new sst.aws.Vpc("MyVpc", { nat: "managed" });

// Store SurrealDB data in EFS

const efs = new sst.aws.Efs("MyEfs", { vpc });

// Run SurrealDB server in a container

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

const server = new sst.aws.Service("MyService", {

cluster,

architecture: "arm64",

image: "surrealdb/surrealdb:v2.0.2",

command: [\
\
    "start",\
\
    "--bind",\
\
    $interpolate`0.0.0.0:${PORT}`,\
\
    "--log",\
\
    "info",\
\
    "--user",\
\
    USERNAME,\
\
    "--pass",\
\
    PASSWORD,\
\
    "surrealkv://data/data.skv",\
\
    "--allow-scripting",\
\
],

volumes: [{ efs, path: "/data" }],

});

// Lambda client to connect to SurrealDB

const config = new sst.Linkable("MyConfig", {

properties: {

username: USERNAME,

password: PASSWORD,

namespace: NAMESPACE,

database: DATABASE,

port: PORT,

host: server.service,

},

});

new sst.aws.Function("MyApp", {

handler: "index.handler",

link: [config],

url: true,

vpc,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-efs-surrealdb).

* * *

## [AWS EFS](https://sst.dev/docs/examples/\#aws-efs)

Mount an EFS file system to a function and a container.

This allows both your function and the container to access the same file system. Here they
both update a counter that’s stored in the file system.

```
await writeFile("/mnt/efs/counter", newValue.toString());
```

The file system is mounted to `/mnt/efs` in both the function and the container.

```
// NAT Gateways are required for Lambda functions

const vpc = new sst.aws.Vpc("MyVpc", { nat: "managed" });

// Create an EFS file system to store a counter

const efs = new sst.aws.Efs("MyEfs", { vpc });

// Create a Lambda function that increments the counter

new sst.aws.Function("MyFunction", {

handler: "lambda.handler",

url: true,

vpc,

volume: {

efs,

path: "/mnt/efs",

},

});

// Create a service that increments the same counter

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

new sst.aws.Service("MyService", {

cluster,

loadBalancer: {

ports: [{ listen: "80/http" }],

},

volumes: [\
\
    {\
\
      efs,\
\
      path: "/mnt/efs",\
\
    },\
\
],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-efs).

* * *

## [AWS Express Redis](https://sst.dev/docs/examples/\#aws-express-redis)

Creates a hit counter app with Express and Redis.

This deploys Express as a Fargate service to ECS and it’s linked to Redis.

```
new sst.aws.Service("MyService", {

cluster,

loadBalancer: {

ports: [{ listen: "80/http" }],

},

dev: {

command: "node --watch index.mjs",

},

link: [redis],

});
```

Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local
machine.

```
sudo npx sst tunnel install
```

This needs _sudo_ to create a network interface on your machine. You’ll only need to do this
once on your machine.

To start your app locally run.

```
npx sst dev
```

Now if you go to `http://localhost:80` you’ll see a counter update as you refresh the page.

Finally, you can deploy it using `npx sst deploy --stage production` using a `Dockerfile`
that’s included in the example.

```
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });

const redis = new sst.aws.Redis("MyRedis", { vpc });

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

new sst.aws.Service("MyService", {

cluster,

link: [redis],

loadBalancer: {

ports: [{ listen: "80/http" }],

},

dev: {

command: "node --watch index.mjs",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-express-redis).

* * *

## [FastAPI](https://sst.dev/docs/examples/\#fastapi)

Deploy a Python FastAPI app as a Lambda function with a linked value.

```
const linkableValue = new sst.Linkable("MyLinkableValue", {

properties: {

foo: "Hello World",

},

});

const fastapi = new sst.aws.Function("FastAPI", {

handler: "functions/src/functions/api.handler",

runtime: "python3.11",

url: true,

link: [linkableValue],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-fastapi).

* * *

## [FFmpeg in Lambda](https://sst.dev/docs/examples/\#ffmpeg-in-lambda)

Uses [FFmpeg](https://ffmpeg.org/) to process videos. In this example, it takes a `clip.mp4`
and grabs a single frame from it.

We use the [`ffmpeg-static`](https://www.npmjs.com/package/ffmpeg-static) package that
contains pre-built binaries for all architectures.

```
import ffmpeg from "ffmpeg-static";
```

We can use this to spawn a child process and run FFmpeg.

```
spawnSync(ffmpeg, ffmpegParams, { stdio: "pipe" });
```

We don’t need a layer when we deploy this because SST will use the right binary for the
target Lambda architecture; including `arm64`.

```
{

nodejs: { install: ["ffmpeg-static"] }

}
```

All this is handled by [`nodejs.install`](https://sst.dev/docs/component/aws/function#nodejs-install).

```
const func = new sst.aws.Function("MyFunction", {

url: true,

memory: "2 GB",

timeout: "15 minutes",

handler: "index.handler",

copyFiles: [{ from: "clip.mp4" }],

nodejs: { install: ["ffmpeg-static"] },

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-ffmpeg).

* * *

## [Flutter web](https://sst.dev/docs/examples/\#flutter-web)

Deploy a Flutter web app as a static site to S3 and CloudFront.

```
new sst.aws.StaticSite("MySite", {

build: {

command: "flutter build web",

output: "build/web",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-flutter-web).

* * *

## [AWS ApiGatewayV2 Go](https://sst.dev/docs/examples/\#aws-apigatewayv2-go)

Uses [aws-lambda-go-api-proxy](https://github.com/awslabs/aws-lambda-go-api-proxy/tree/master) to allow you to run a Go API with API Gateway V2.

So you write your Go function as you normally would and then use the package to handle the API Gateway V2 event.

```
import (

"github.com/aws/aws-lambda-go/lambda"

"github.com/awslabs/aws-lambda-go-api-proxy/httpadapter"

)

func router() *http.ServeMux {

mux := http.NewServeMux()

mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

w.Header().Set("Content-Type", "application/json")

w.WriteHeader(http.StatusOK)

w.Write([]byte(`{"message": "hello world"}`))

})

return mux

}

func main() {

lambda.Start(httpadapter.NewV2(router()).ProxyWithContext)

}
```

```
const api = new sst.aws.ApiGatewayV2("GoApi");

api.route("$default", {

handler: "src/",

runtime: "go",

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-go-api-gateway-v2).

* * *

## [AWS Lambda Go S3 Presigned](https://sst.dev/docs/examples/\#aws-lambda-go-s3-presigned)

Generates a presigned URL for the linked S3 bucket in a Go Lambda function.

Configure the S3 Client and the PresignedClient.

```
cfg, err := config.LoadDefaultConfig(context.TODO())

if err != nil {

panic(err)

}

client := s3.NewFromConfig(cfg)

presignedClient := s3.NewPresignClient(client)
```

Generate the presigned URL.

```
bucketName, err := resource.Get("Bucket", "name")

if err != nil {

panic(err)

}

url, err := presignedClient.PresignPutObject(context.TODO(), &s3.PutObjectInput{

Bucket: aws.String(bucket.(string)),

Key:    aws.String(key),

})
```

```
const bucket = new sst.aws.Bucket("Bucket");

const api = new sst.aws.ApiGatewayV2("Api");

api.route("GET /upload-url", {

handler: "src/",

runtime: "go",

link: [bucket],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-go-lambda-bucket-presigned-url).

* * *

## [AWS Lambda Go DynamoDB](https://sst.dev/docs/examples/\#aws-lambda-go-dynamodb)

An example on how to use a Go runtime Lambda with DynamoDB.

You configure the DynamoDB client.

```
import (

"github.com/sst/sst/v3/sdk/golang/resource"

)

func main() {

cfg, err := config.LoadDefaultConfig(context.Background())

if err != nil {

panic(err)

}

client := dynamodb.NewFromConfig(cfg)

tableName, err := resource.Get("Table", "name")

if err != nil {

panic(err)

}

}
```

And make a request to DynamoDB.

```
_, err = r.client.PutItem(ctx, &dynamodb.PutItemInput{

TableName: tableName.(string),

Item:      item,

})
```

```
const table = new sst.aws.Dynamo("Table", {

fields: {

PK: "string",

SK: "string",

},

primaryIndex: { hashKey: "PK", rangeKey: "SK" },

});

new sst.aws.Function("GoFunction", {

url: true,

runtime: "go",

handler: "./src",

link: [table],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-go-lambda-dynamo).

* * *

## [AWS Hono container with Redis](https://sst.dev/docs/examples/\#aws-hono-container-with-redis)

Creates a hit counter app with Hono and Redis.

This deploys Hono API as a Fargate service to ECS and it’s linked to Redis.

```
new sst.aws.Service("MyService", {

cluster,

link: [redis],

loadBalancer: {

ports: [{ listen: "80/http", forward: "3000/http" }],

},

dev: {

command: "npm run dev",

},

});
```

Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local
machine.

```
sudo npx sst tunnel install
```

This needs _sudo_ to create a network interface on your machine. You’ll only need to do this
once on your machine.

To start your app locally run.

```
npx sst dev
```

Now if you go to `http://localhost:3000` you’ll see a counter update as you refresh the page.

Finally, you can deploy it by:

1. Using the `Dockerfile` that’s included in this example.

2. This compiles our TypeScript file, so we’ll need add the following to the `tsconfig.json`.

```
   {

"compilerOptions": {

// ...

"outDir": "./dist"

},

"exclude": ["node_modules"]

}
   ```

3. Install TypeScript.

```
   npm install typescript --save-dev
   ```

4. And add a `build` script to our `package.json`.

```
   "scripts": {

// ...

"build": "tsc"

}
   ```

And finally, running `npx sst deploy --stage production`.

```
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });

const redis = new sst.aws.Redis("MyRedis", { vpc });

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

new sst.aws.Service("MyService", {

cluster,

link: [redis],

loadBalancer: {

ports: [{ listen: "80/http", forward: "3000/http" }],

},

dev: {

command: "npm run dev",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-hono-redis).

* * *

## [AWS Hono streaming](https://sst.dev/docs/examples/\#aws-hono-streaming)

An example on how to enable streaming for Lambda functions using Hono.

```
{

streaming: true

}
```

```
export const handler = streamHandle(app);
```

To test this in your terminal, use the `curl` command with the `--no-buffer` option.

```
curl --no-buffer https://u3dyblk457ghskwbmzrbylpxoi0ayrbb.lambda-url.us-east-1.on.aws
```

Streaming is also supported through API Gateway REST API.

```
const hono = new sst.aws.Function("Hono", {

url: true,

streaming: true,

timeout: "15 minutes",

handler: "index.handler",

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-hono-stream).

* * *

## [IAM permissions boundaries](https://sst.dev/docs/examples/\#iam-permissions-boundaries)

Use permissions boundaries to set the maximum permissions for all IAM roles that’ll be
created in your app.

In this example, the Function has the `s3:ListAllMyBuckets` and `sqs:ListQueues`
permissions. However, we create a permissions boundary that only allows `s3:ListAllMyBuckets`.
And we apply it to all Roles in the app using the global
[`$transform`](https://sst.dev/docs/reference/global/#transform).

As a result, the Function is only allowed to list S3 buckets. If you open the deployed URL,
you’ll see that the SQS list call fails.

Learn more about [AWS IAM permissions boundaries](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html).

```
// Create a permission boundary

const permissionsBoundary = new aws.iam.Policy("MyPermissionsBoundary", {

policy: aws.iam.getPolicyDocumentOutput({

statements: [\
\
      {\
\
        actions: ["s3:ListAllMyBuckets"],\
\
        resources: ["*"],\
\
      },\
\
    ],

}).json,

});

// Apply the boundary to all roles

$transform(aws.iam.Role, (args) => {

args.permissionsBoundary = permissionsBoundary;

});

// The boundary automatically applies to this Function's role

const app = new sst.aws.Function("MyApp", {

handler: "index.handler",

permissions: [\
\
    {\
\
      actions: ["s3:ListAllMyBuckets", "sqs:ListQueues"],\
\
      resources: ["*"],\
\
    },\
\
],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-iam-permission-boundary).

* * *

## [Import existing resource](https://sst.dev/docs/examples/\#import-existing-resource)

Import an existing AWS resource using the `transform` option with `opts.import`.

```
new sst.aws.Bucket("MyBucket", {

transform: {

bucket(args, opts) {

opts.import = "aws-import-my-bucket";

args.bucket = "aws-import-my-bucket";

args.forceDestroy = undefined;

},

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-import).

* * *

## [Current AWS account](https://sst.dev/docs/examples/\#current-aws-account)

You can use the `aws.getXXXXOutput()` provider functions to get info about the current
AWS account.
Learn more about [provider functions](https://sst.dev/docs/providers/#functions).

```

```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-info).

* * *

## [AWS JSX Email](https://sst.dev/docs/examples/\#aws-jsx-email)

Uses [JSX Email](https://jsx.email/) and the `Email` component to design and send emails.

To test this example, change the `sst.config.ts` to use your own email address.

```
sender: "email@example.com"
```

Then run.

```
npm install

npx sst dev
```

You’ll get an email from AWS asking you to confirm your email address. Click the link to
verify it.

Next, go to the URL in the `sst dev` CLI output. You should now receive an email rendered
using JSX Email.

```
import { Template } from "./templates/email";

await render(Template({

email: "spongebob@example.com",

name: "Spongebob Squarepants"

}))
```

Once you are ready to go to production, you can:

- [Request production access](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html) for SES
- And [use your domain](https://sst.dev/docs/component/aws/email/) to send emails

```
const email = new sst.aws.Email("MyEmail", {

sender: "email@example.com",

});

const api = new sst.aws.Function("MyApi", {

handler: "index.handler",

link: [email],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-jsx-email).

* * *

## [Kinesis streams](https://sst.dev/docs/examples/\#kinesis-streams)

Create a Kinesis stream, and subscribe to it with a function.

```
const stream = new sst.aws.KinesisStream("MyStream");

// Create a function subscribing to all events

stream.subscribe("AllSub", "subscriber.all");

// Create a function subscribing to events of `bar` type

stream.subscribe("FilteredSub", "subscriber.filtered", {

filters: [\
\
    {\
\
      data: {\
\
        type: ["bar"],\
\
      },\
\
    },\
\
],

});

const app = new sst.aws.Function("MyApp", {

handler: "publisher.handler",

link: [stream],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-kinesis-stream).

* * *

## [AWS Lambda AI streaming](https://sst.dev/docs/examples/\#aws-lambda-ai-streaming)

An example on how to stream AI responses from a Lambda function using the
[AI SDK](https://ai-sdk.dev/).

Uses `streamText` from the AI SDK to stream a response
through a Lambda function URL.

```
{

streaming: true

}
```

The handler uses `awslambda.streamifyResponse` to stream the AI response
back to the client as it’s generated.

```
export const handler = awslambda.streamifyResponse(

async (_event, responseStream) => {

const result = streamText({

model: "amazon/nova-micro",

prompt: "Write a poem about clouds that is twenty paragraphs long.",

});

responseStream.setContentType("text/plain");

for await (const chunk of result.textStream) {

responseStream.write(chunk);

}

responseStream.end();

},

);
```

Set the API key for the AI gateway.

```
sst secret set AiGatewayApiKey your-api-key-here
```

Use the “Run Client” dev command in the multiplexer to invoke the server and see
the streamed response.

```
const server = new sst.aws.Function("Server", {

url: true,

streaming: true,

timeout: "15 minutes",

handler: "index.handler",

environment: {

AI_GATEWAY_API_KEY: new sst.Secret("AiGatewayApiKey").value,

},

});

new sst.x.DevCommand("Client", {

dev: {

autostart: false,

command: $interpolate`curl --no-buffer ${server.url}`,

title: "Run Client",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-lambda-ai-stream).

* * *

## [AWS Lambda Go](https://sst.dev/docs/examples/\#aws-lambda-go)

This example shows how to use the [`go`](https://golang.org/) runtime in your Lambda
functions.

Our Go function is in the `src` directory and we point to it in our function.

```
new sst.aws.Function("MyFunction", {

url: true,

runtime: "go",

link: [bucket],

handler: "./src",

});
```

We are also linking it to an S3 bucket. We can reference the bucket in our function.

```
func handler() (string, error) {

bucket, err := resource.Get("MyBucket", "name")

if err != nil {

return "", err

}

return bucket.(string), nil

}
```

The `resource.Get` function is from the SST Go SDK.

```
import (

"github.com/sst/sst/v3/sdk/golang/resource"

)
```

The `sst dev` CLI also supports running your Go function [_Live_](https://sst.dev/docs/live).

```
const bucket = new sst.aws.Bucket("MyBucket");

new sst.aws.Function("MyFunction", {

url: true,

runtime: "go",

link: [bucket],

handler: "./src",

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-lambda-golang).

* * *

## [AWS Lambda build hook](https://sst.dev/docs/examples/\#aws-lambda-build-hook)

In this example we hook into the Lambda function build process with
`hook.postbuild`.

This is useful for modifying the generated Lambda function code before it’s
uploaded to AWS. It can also be used for uploading the generated sourcemaps
to a service like Sentry.

```
new sst.aws.Function("MyFunction", {

url: true,

handler: "index.handler",

hook: {

async postbuild(dir) {

console.log(`postbuild ------- ${dir}`);

},

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-lambda-hook).

* * *

## [AWS Lambda retry with queues](https://sst.dev/docs/examples/\#aws-lambda-retry-with-queues)

An example on how to retry Lambda invocations using SQS queues.

Create a SQS retry queue which will be set as the destination for the Lambda function.

```
const retryQueue = new sst.aws.Queue("retryQueue");

const bus = new sst.aws.Bus("bus");

const busSubscriber = bus.subscribe("busSubscriber", {

handler: "src/bus-subscriber.handler",

environment: {

RETRIES: "2", // set the number of retries

},

link: [retryQueue], // so the function can send messages to the retry queue

});

new aws.lambda.FunctionEventInvokeConfig("eventConfig", {

functionName: $resolve([busSubscriber.nodes.function.name]).apply(

([name]) => name,

),

maximumRetryAttempts: 2, // default is 2, must be between 0 and 2

destinationConfig: {

onFailure: {

destination: retryQueue.arn,

},

},

});
```

Create a bus subscriber which will publish messages to the bus. Include a DLQ for messages that continue to fail.

```
const dlq = new sst.aws.Queue("dlq");

retryQueue.subscribe({

handler: "src/retry.handler",

link: [busSubscriber.nodes.function, retryQueue, dlq],

timeout: "30 seconds",

environment: {

RETRIER_QUEUE_URL: retryQueue.url,

},

permissions: [\
\
    {\
\
      actions: ["lambda:GetFunction", "lambda:InvokeFunction"],\
\
      resources: [\
\
        $interpolate`arn:aws:lambda:${aws.getRegionOutput().region}:${\
\
          aws.getCallerIdentityOutput().accountId\
\
        }:function:*`,\
\
      ],\
\
    },\
\
],

transform: {

function: {

deadLetterConfig: {

targetArn: dlq.arn,

},

},

},

});
```

The Retry function will read mesaages and send back to the queue to be retried with a backoff.

```
export const handler: SQSHandler = async (evt) => {

for (const record of evt.Records) {

const parsed = JSON.parse(record.body);

console.log("body", parsed);

const functionName = parsed.requestContext.functionArn

.replace(":$LATEST", "")

.split(":")

.pop();

if (parsed.responsePayload) {

const attempt = (parsed.requestPayload.attempts || 0) + 1;

const info = await lambda.send(

new GetFunctionCommand({

FunctionName: functionName,

}),

);

const max =

Number.parseInt(

info.Configuration?.Environment?.Variables?.RETRIES || "",

) || 0;

console.log("max retries", max);

if (attempt > max) {

console.log(`giving up after ${attempt} retries`);

// send to dlq

await sqs.send(

new SendMessageCommand({

QueueUrl: Resource.dlq.url,

MessageBody: JSON.stringify({

requestPayload: parsed.requestPayload,

requestContext: parsed.requestContext,

responsePayload: parsed.responsePayload,

}),

}),

);

return;

}

const seconds = Math.min(Math.pow(2, attempt), 900);

console.log(

"delaying retry by ",

seconds,

"seconds for attempt",

attempt,

);

parsed.requestPayload.attempts = attempt;

await sqs.send(

new SendMessageCommand({

QueueUrl: Resource.retryQueue.url,

DelaySeconds: seconds,

MessageBody: JSON.stringify({

requestPayload: parsed.requestPayload,

requestContext: parsed.requestContext,

}),

}),

);

}

if (!parsed.responsePayload) {

console.log("triggering function");

try {

await lambda.send(

new InvokeCommand({

InvocationType: "Event",

Payload: Buffer.from(JSON.stringify(parsed.requestPayload)),

FunctionName: functionName,

}),

);

} catch (e) {

if (e instanceof ResourceNotFoundException) {

return;

}

throw e;

}

}

}

};
```

```
const dlq = new sst.aws.Queue("dlq");

const retryQueue = new sst.aws.Queue("retryQueue");

const bus = new sst.aws.Bus("bus");

const busSubscriber = bus.subscribe("busSubscriber", {

handler: "src/bus-subscriber.handler",

environment: {

RETRIES: "2",

},

link: [retryQueue], // so the function can send messages to the queue

});

const publisher = new sst.aws.Function("publisher", {

handler: "src/publisher.handler",

link: [bus],

url: true,

});

new aws.lambda.FunctionEventInvokeConfig("eventConfig", {

functionName: $resolve([busSubscriber.nodes.function.name]).apply(

([name]) => name,

),

maximumRetryAttempts: 1,

destinationConfig: {

onFailure: {

destination: retryQueue.arn,

},

},

});

retryQueue.subscribe({

handler: "src/retry.handler",

link: [busSubscriber.nodes.function, retryQueue, dlq],

timeout: "30 seconds",

environment: {

RETRIER_QUEUE_URL: retryQueue.url,

},

transform: {

function: {

deadLetterConfig: {

targetArn: dlq.arn,

},

},

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-lambda-retry-with-queues).

* * *

## [AWS Lamda Rust multiple-binaries](https://sst.dev/docs/examples/\#aws-lamda-rust-multiple-binaries)

This example shows how to deploy multiple binary rust project to AWS Lambda.

SST relies on the work of [cargo lambda](https://cargo-lambda/) to build and deploy Rust Lambda functions.

What is special about the following file is that we are defining multiple binaries using the `[[bin]]` section in the `Cargo.toml` file.

```
[package]

name = "aws-lambda-rust-multi-bin"

version = "0.1.0"

edition = "2021"

[dependencies]

lambda_runtime = "0.13.0"

serde = { version = "1.0.217", features = ["derive"] }

serde_json = "1.0.138"

tokio = { version = "1", features = ["macros"] }

# -- please note ommited dependencies --

[[bin]]

name = "push"

path = "src/push.rs"

[[bin]]

name = "pop"

path = "src/pop.rs"
```

We then utilise the . syntax to specify the handler binary

```
new sst.aws.Function("push", {

url: true,

runtime: "rust",

link: [bucket],

handler: "./.push",

});

new sst.aws.Function("pop", {

url: true,

runtime: "rust",

link: [bucket],

handler: "./.pop",

});
```

```
const bucket = new sst.aws.Bucket("Bucket");

const push = new sst.aws.Function("push", {

runtime: "rust",

handler: "./.push",

url: true,

architecture: "arm64",

link: [bucket],

});

const pop = new sst.aws.Function("pop", {

runtime: "rust",

handler: "./.pop",

url: true,

architecture: "arm64",

link: [bucket],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-lambda-rust-multiple-binaries).

* * *

## [AWS Lambda streaming](https://sst.dev/docs/examples/\#aws-lambda-streaming)

An example on how to enable streaming for Lambda functions.

```
{

streaming: true

}
```

Use the `awslambda.streamifyResponse` function to wrap your handler. The `awslambda`
global is provided by the Lambda execution environment at runtime, and SST provides it
automatically during `sst dev` as well. For TypeScript types, importing from
`@types/aws-lambda` will augment the global namespace.

```
export const handler = awslambda.streamifyResponse(

async (event, stream) => {

stream = awslambda.HttpResponseStream.from(stream, {

statusCode: 200,

headers: {

"Content-Type": "text/plain; charset=UTF-8",

"X-Content-Type-Options": "nosniff",

},

});

stream.write("Hello ");

await new Promise((resolve) => setTimeout(resolve, 3000));

stream.write("World");

stream.end();

},

);
```

```
const fn = new sst.aws.Function("MyFunction", {

url: true,

streaming: true,

timeout: "15 minutes",

handler: "index.handler",

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-lambda-stream).

* * *

## [AWS Lambda tRPC streaming](https://sst.dev/docs/examples/\#aws-lambda-trpc-streaming)

An example on how to use tRPC with Lambda streaming.

Uses `@trpc/server`’s `awsLambdaStreamingRequestHandler` adapter to handle
streaming responses through Lambda function URLs.

The `trpc-server` function defines a tRPC router and streams responses.
The `trpc-client` function invokes the server using `httpBatchStreamLink`.

Streaming is supported in both `sst dev` and `sst deploy`.

```
const trpcServer = new sst.aws.Function('TrpcServer', {

handler: 'trpc-server.handler',

streaming: true,

url: true,

runtime: 'nodejs24.x',

});

new sst.x.DevCommand('Client', {

dev: {

autostart: false,

command: $interpolate`npx tsx trpc-client.ts`,

title: 'Run Client',

},

environment: {

TRPC_SERVER_URL: trpcServer.url,

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-lambda-trpc-stream).

* * *

## [AWS Lambda in a VPC](https://sst.dev/docs/examples/\#aws-lambda-in-a-vpc)

You can use SST to locally work on Lambda functions that are in a VPC. To do so, you’ll
need to enable `bastion` and `nat` on the `Vpc` component.

```
new sst.aws.Vpc("MyVpc", { bastion: true, nat: "managed" });
```

The NAT gateway is necessary to allow your Lambda function to connect to the internet. While,
the bastion host is necessary for your local machine to be able to tunnel to the VPC.

You’ll need to install the tunnel, if you haven’t done this before.

```
sudo sst tunnel install
```

This needs _sudo_ to create the network interface on your machine. You’ll only need to do
this once.

Now you can run `sst dev`, your function can access resources in the VPC. For example, here
we are connecting to a Redis cluster.

```
const redis = new Cluster(

[{ host: Resource.MyRedis.host, port: Resource.MyRedis.port }],

{

dnsLookup: (address, callback) => callback(null, address),

redisOptions: {

tls: {},

username: Resource.MyRedis.username,

password: Resource.MyRedis.password,

},

}

);
```

The Redis cluster is in the same VPC as the function.

```
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true, nat: "managed" });

const redis = new sst.aws.Redis("MyRedis", { vpc });

const api = new sst.aws.Function("MyFunction", {

vpc,

url: true,

link: [redis],

handler: "index.handler"

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-lambda-vpc).

* * *

## [Linkable env vars](https://sst.dev/docs/examples/\#linkable-env-vars)

Pass SST link env vars to a native `aws.ecs.TaskDefinition` container using
`sst.Linkable.env()`. This lets `Resource.MyResource` work at runtime
in compute not managed by SST.

```
// Create an SST bucket

const bucket = new sst.aws.Bucket("MyBucket");

// Create a custom linkable

const linkable = new sst.Linkable("MyLinkable", {

properties: {

foo: "bar",

},

});

// Create VPC and ECS cluster using native AWS resources

const vpc = new aws.ec2.Vpc("Vpc", { cidrBlock: "10.0.0.0/16" });

const subnet = new aws.ec2.Subnet("Subnet", {

vpcId: vpc.id,

cidrBlock: "10.0.0.0/24",

});

const cluster = new aws.ecs.Cluster("Cluster");

// Linkable.env() returns a Record<string, string>, but ECS expects

// environment as an array of { name, value } objects

const environment = sst.Linkable.env([bucket, linkable]).apply((env) =>

Object.entries(env).map(([name, value]) => ({ name, value })),

);

const taskDefinition = new aws.ecs.TaskDefinition("TaskDefinition", {

family: $interpolate`${$app.name}-${$app.stage}`,

cpu: "256",

memory: "512",

networkMode: "awsvpc",

requiresCompatibilities: ["FARGATE"],

containerDefinitions: $jsonStringify([\
\
    {\
\
      name: "app",\
\
      image: "public.ecr.aws/docker/library/node:20-slim",\
\
      essential: true,\
\
      environment,\
\
    },\
\
]),

});

new aws.ecs.Service("Service", {

cluster: cluster.arn,

taskDefinition: taskDefinition.arn,

desiredCount: 0,

launchType: "FARGATE",

networkConfiguration: {

subnets: [subnet.id],

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-linkable-env).

* * *

## [AWS Load Balancer Web Application Firewall (WAF)](https://sst.dev/docs/examples/\#aws-load-balancer-web-application-firewall-waf)

Enable WAF for an AWS Load Balancer.

The WAF is configured to enable a rate limit and enables AWS managed rules.

```
const vpc = new sst.aws.Vpc("MyVpc");

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

const service = cluster.addService("MyAppService", {

image: {

context: "./",

dockerfile: "packages/server/Dockerfile",

},

});

const rateLimitRule = {

name: "RateLimitRule",

statement: {

rateBasedStatement: {

limit: 200,

aggregateKeyType: "IP",

},

},

priority: 1,

action: { block: {} },

visibilityConfig: {

cloudwatchMetricsEnabled: true,

sampledRequestsEnabled: true,

metricName: "MyAppRateLimitRule",

},

};

const awsManagedRules = {

name: "AWSManagedRules",

statement: {

managedRuleGroupStatement: {

name: "AWSManagedRulesCommonRuleSet",

vendorName: "AWS",

},

},

priority: 2,

overrideAction: {

none: {},

},

visibilityConfig: {

cloudwatchMetricsEnabled: true,

sampledRequestsEnabled: true,

metricName: "MyAppAWSManagedRules",

},

};

const webAcl = new aws.wafv2.WebAcl("AppAlbWebAcl", {

defaultAction: { allow: {} },

scope: "REGIONAL",

visibilityConfig: {

cloudwatchMetricsEnabled: true,

sampledRequestsEnabled: true,

metricName: "AppAlbWebAcl",

},

rules: [rateLimitRule, awsManagedRules],

});

service.nodes.loadBalancer.arn.apply((arn) => {

new aws.wafv2.WebAclAssociation("MyAppAlbWebAclAssociation", {

resourceArn: arn,

webAclArn: webAcl.arn,

});

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-load-balancer-waf).

* * *

## [AWS multi-region](https://sst.dev/docs/examples/\#aws-multi-region)

To deploy resources to multiple AWS regions, you can create a new provider for the region
you want to deploy to.

```
const provider = new aws.Provider("MyProvider", { region: "us-west-2" });
```

And then pass that in to the resource.

```
new sst.aws.Function("MyFunction", { handler: "index.handler" }, { provider });
```

If no provider is passed in, the default provider will be used. And if no region is
specified, the default region from your credentials will be used.

```
const east = new sst.aws.Function("MyEastFunction", {

url: true,

handler: "index.handler",

});

const provider = new aws.Provider("MyWestProvider", { region: "us-west-2" });

const west = new sst.aws.Function(

"MyWestFunction",

{

url: true,

handler: "index.handler",

},

{ provider }

);
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-multi-region).

* * *

## [AWS MySQL local](https://sst.dev/docs/examples/\#aws-mysql-local)

In this example, we connect to a locally running MySQL instance for dev. While
on deploy, we use RDS.

We use the [`docker run`](https://docs.docker.com/reference/cli/docker/container/run/) CLI
to start a local container with MySQL. You don’t have to use Docker, you can use
any other way to run MySQL locally.

```
docker run \

--rm \

-p 3306:3306 \

-v $(pwd)/.sst/storage/mysql:/var/lib/mysql/data \

-e MYSQL_ROOT_PASSWORD=password \

-e MYSQL_DATABASE=local \

mysql:8.0
```

The data is saved to the `.sst/storage` directory. So if you restart the dev server, the
data will still be there.

We then configure the `dev` property of the `Mysql` component with the settings for the
local MySQL instance.

```
dev: {

username: "root",

password: "password",

database: "local",

host: "localhost",

port: 3306,

}
```

By providing the `dev` prop for Mysql, SST will use the local MySQL instance and
not deploy a new RDS database when running `sst dev`.

It also allows us to access the database through a Resource `link` without having to
conditionally check if we are running locally.

```
const pool = new Pool({

host: Resource.MyDatabase.host,

port: Resource.MyDatabase.port,

user: Resource.MyDatabase.username,

password: Resource.MyDatabase.password,

database: Resource.MyDatabase.database,

});
```

The above will work in both `sst dev` and `sst deploy`.

```
const vpc = new sst.aws.Vpc("MyVpc", { nat: "ec2" });

const mysql = new sst.aws.Mysql("MyDatabase", {

dev: {

username: "root",

password: "password",

database: "local",

host: "localhost",

port: 3306,

},

vpc,

});

new sst.aws.Function("MyFunction", {

vpc,

url: true,

link: [mysql],

handler: "index.handler",

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-mysql-local).

* * *

## [AWS MySQL](https://sst.dev/docs/examples/\#aws-mysql)

In this example, we deploy an RDS MySQL database.

```
const mysql = new sst.aws.Mysql("MyDatabase", {

vpc,

});
```

And link it to a Lambda function.

```
new sst.aws.Function("MyApp", {

handler: "index.handler",

link: [mysql],

url: true,

vpc,

});
```

Now in the function we can access the database.

```
const connection = await mysql.createConnection({

database: Resource.MyDatabase.database,

host: Resource.MyDatabase.host,

port: Resource.MyDatabase.port,

user: Resource.MyDatabase.username,

password: Resource.MyDatabase.password,

});
```

We also enable the `bastion` option for the VPC. This allows us to connect to
the database from our local machine with the `sst tunnel` CLI.

```
sudo npx sst tunnel install
```

This needs _sudo_ to create a network interface on your machine. You’ll only
need to do this once on your machine.

Now you can run `npx sst dev` and you can connect to the database from your local machine.

```
const vpc = new sst.aws.Vpc("MyVpc", { nat: "ec2", bastion: true });

const mysql = new sst.aws.Mysql("MyDatabase", {

vpc,

});

const app = new sst.aws.Function("MyApp", {

handler: "index.handler",

link: [mysql],

url: true,

vpc,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-mysql).

* * *

## [AWS NestJS with Redis](https://sst.dev/docs/examples/\#aws-nestjs-with-redis)

Creates a hit counter app with NestJS and Redis.

Also make sure you have Node 22.12. Or set the `--experimental-require-module` flag.
This’ll allow NestJS to import the SST SDK.

This deploys NestJS as a Fargate service to ECS and it’s linked to Redis.

```
new sst.aws.Service("MyService", {

cluster,

link: [redis],

loadBalancer: {

ports: [{ listen: "80/http", forward: "3000/http" }],

},

dev: {

command: "npm run start:dev",

},

});
```

Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local
machine.

```
sudo npx sst tunnel install
```

This needs _sudo_ to create a network interface on your machine. You’ll only need to do this
once on your machine.

To start your app locally run.

```
npx sst dev
```

Now if you go to `http://localhost:3000` you’ll see a counter update as you refresh the page.

Finally, you can deploy it using `npx sst deploy --stage production` using a `Dockerfile`
that’s included in the example.

```
const vpc = new sst.aws.Vpc('MyVpc', { bastion: true });

const redis = new sst.aws.Redis('MyRedis', { vpc });

const cluster = new sst.aws.Cluster('MyCluster', { vpc });

new sst.aws.Service('MyService', {

cluster,

link: [redis],

loadBalancer: {

ports: [{ listen: '80/http', forward: '3000/http' }],

},

dev: {

command: 'npm run start:dev',

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-nestjs-redis).

* * *

## [AWS Next.js add behavior](https://sst.dev/docs/examples/\#aws-nextjs-add-behavior)

Here’s how to add additional routes or cache behaviors to the CDN of a Next.js app deployed
with OpenNext to AWS.

Specify the path pattern that you want to forward to your new origin. For example, to forward
all requests to the `/blog` path to a different origin.

```
pathPattern: "/blog/*"
```

And then specify the domain of the new origin.

```
domainName: "blog.example.com"
```

We use this to `transform` our site’s CDN and add the additional behaviors.

```
const blogOrigin = {

// The domain of the new origin

domainName: "blog.example.com",

originId: "blogCustomOrigin",

customOriginConfig: {

httpPort: 80,

httpsPort: 443,

originSslProtocols: ["TLSv1.2"],

// If HTTPS is supported

originProtocolPolicy: "https-only",

},

};

const cacheBehavior = {

// The path to forward to the new origin

pathPattern: "/blog/*",

targetOriginId: blogOrigin.originId,

viewerProtocolPolicy: "redirect-to-https",

allowedMethods: ["GET", "HEAD", "OPTIONS"],

cachedMethods: ["GET", "HEAD"],

forwardedValues: {

queryString: true,

cookies: {

forward: "all",

},

},

};

new sst.aws.Nextjs("MyWeb", {

transform: {

cdn: (options: sst.aws.CdnArgs) => {

options.origins = $resolve(options.origins).apply(val => [...val, blogOrigin]);

options.orderedCacheBehaviors = $resolve(

options.orderedCacheBehaviors || []

).apply(val => [...val, cacheBehavior]);

},

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-nextjs-add-behavior).

* * *

## [AWS Next.js basic auth](https://sst.dev/docs/examples/\#aws-nextjs-basic-auth)

Deploys a simple Next.js app and adds basic auth to it.

This is useful for dev environments where you want to share your app your team but ensure
that it’s not publicly accessible.

This works by injecting some code into a CloudFront function that checks the basic auth
header and matches it against the `USERNAME` and `PASSWORD` secrets.

```
{

injection: $interpolate`

if (

!event.request.headers.authorization

|| event.request.headers.authorization.value !== "Basic ${basicAuth}"

) {

return {

statusCode: 401,

headers: {

"www-authenticate": { value: "Basic" }

}

};

}`,

}
```

To deploy this, you need to first set the `USERNAME` and `PASSWORD` secrets.

```
sst secret set USERNAME my-username

sst secret set PASSWORD my-password
```

If you are deploying this to preview environments, you might want to set the secrets using
the [`--fallback`](https://sst.dev/docs/reference/cli#secret) flag.

```
const username = new sst.Secret("USERNAME");

const password = new sst.Secret("PASSWORD");

const basicAuth = $resolve([username.value, password.value]).apply(

([username, password]) =>

Buffer.from(`${username}:${password}`).toString("base64")

);

new sst.aws.Nextjs("MyWeb", {

server: {

// Don't password protect prod

edge: $app.stage !== "production"

? {

viewerRequest: {

injection: $interpolate`

if (

!event.request.headers.authorization

|| event.request.headers.authorization.value !== "Basic ${basicAuth}"

) {

return {

statusCode: 401,

headers: {

"www-authenticate": { value: "Basic" }

}

};

}`,

},

}

: undefined,

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-nextjs-basic-auth).

* * *

## [AWS Next.js container with Redis](https://sst.dev/docs/examples/\#aws-nextjs-container-with-redis)

Creates a hit counter app with Next.js and Redis.

This deploys Next.js as a Fargate service to ECS and it’s linked to Redis.

```
new sst.aws.Service("MyService", {

cluster,

link: [redis],

loadBalancer: {

ports: [{ listen: "80/http", forward: "3000/http" }],

},

dev: {

command: "npm run dev",

},

});
```

Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local
machine.

```
sudo npx sst tunnel install
```

This needs _sudo_ to create a network interface on your machine. You’ll only need to do this
once on your machine.

To start your app locally run.

```
npx sst dev
```

Now if you go to `http://localhost:3000` you’ll see a counter update as you refresh the page.

Finally, you can deploy it by:

1. Setting `output: "standalone"` in your `next.config.mjs` file.
2. Adding a `Dockerfile` that’s included in this example.
3. Running `npx sst deploy --stage production`.

```
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });

const redis = new sst.aws.Redis("MyRedis", { vpc });

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

new sst.aws.Service("MyService", {

cluster,

link: [redis],

loadBalancer: {

ports: [{ listen: "80/http", forward: "3000/http" }],

},

dev: {

command: "npm run dev",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-nextjs-redis).

* * *

## [AWS Next.js streaming](https://sst.dev/docs/examples/\#aws-nextjs-streaming)

An example of how to use streaming Next.js RSC. Uses `Suspense` to stream an async component.

```
<Suspense fallback={<div>Loading...</div>}>

<Friends />

</Suspense>
```

For this demo we also need to make sure the route is not statically built.

```
export const dynamic = "force-dynamic";
```

This is deployed with OpenNext, which needs a config to enable streaming.

```
export default {

default: {

override: {

wrapper: "aws-lambda-streaming"

}

}

};
```

You should see the _friends_ section load after a 3 second delay.

```
new sst.aws.Nextjs("MyWeb");
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-nextjs-stream).

* * *

## [AWS Nuxt streaming](https://sst.dev/docs/examples/\#aws-nuxt-streaming)

An example of how to use streaming with Nuxt.js. Uses `createEventStream` to stream data from a server API.

```
export default defineEventHandler(async (event) => {

const eventStream = createEventStream(event);

eventStream.push("Start\n\n");

// Send a message every second

const interval = setInterval(async () => {

await eventStream.push(`Random: ${Math.random().toFixed(5)} `);

}, 1000);

}
```

The client uses the Fetch API to consume the stream.

```
<script setup lang="ts">

const output = ref('')

async function stream() {

output.value = ''

const response = await fetch('/api/streaming')

const reader = response.body?.getReader()

const decoder = new TextDecoder()

let done = false

while (!done && reader) {

const { value, done: readerDone } = await reader.read()

done = readerDone

if (value) {

output.value += decoder.decode(value, { stream: true })

}

}

}

</script>

<template>

<div>

<pre>{{ output }}</pre>

<button @click="stream">Call API</button>

<button @click="output = ''">Clear Output</button>

</div>

</template>
```

Make sure to have your nuxt.config.ts set up to handle the streaming API correctly.

```
export default defineNuxtConfig({

nitro: {

preset: 'aws-lambda',

awsLambda: {

streaming: true

}

}

});
```

You should see random numbers streamed to the page every second for 10 seconds.

```
new sst.aws.Nuxt("MyWeb");
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-nuxt-stream).

* * *

## [AWS OpenSearch local](https://sst.dev/docs/examples/\#aws-opensearch-local)

In this example, we connect to a locally running OpenSearch process for dev. While
on deploy, we use AWS’ OpenSearch Service.

We use the [`docker run`](https://docs.docker.com/reference/cli/docker/container/run/)
CLI to start a local container with OpenSearch. You don’t have to use Docker, you can use
any other way to run OpenSearch locally.

```
docker run \

--rm \

-p 9200:9200 \

-v $(pwd)/.sst/storage/opensearch:/usr/share/opensearch/data \

-e discovery.type=single-node \

-e plugins.security.disabled=true \

-e OPENSEARCH_INITIAL_ADMIN_PASSWORD=^Passw0rd^ \

opensearchproject/opensearch:2.17.0
```

The data is saved to the `.sst/storage` directory. So if you restart the dev server, the
data will still be there.

We then configure the `dev` property of the `OpenSearch` component with the settings for
the local OpenSearch instance.

```
dev: {

url: "http://localhost:9200",

username: "admin",

password: "^Passw0rd^"

}
```

By providing the `dev` prop for OpenSearch, SST will use the local OpenSearch process and
not deploy a new OpenSearch domain when running `sst dev`.

It also allows us to access the local process through a Resource `link` without having
to conditionally check if we are running locally.

```
const client = new Client({

node: Resource.MySearch.url,

auth: {

username: Resource.MySearch.username,

password: Resource.MySearch.password,

},

});
```

The above will work in both `sst dev` and `sst deploy`.

```
const search = new sst.aws.OpenSearch("MySearch", {

dev: {

url: "http://localhost:9200",

username: "admin",

password: "^Passw0rd^",

},

});

new sst.aws.Function("MyApp", {

handler: "index.handler",

url: true,

link: [search],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-open-search-local).

* * *

## [AWS OpenSearch](https://sst.dev/docs/examples/\#aws-opensearch)

In this example we create a new OpenSearch domain, link it to a function, and
then query it.

Start by creating a new OpenSearch domain.

```
const search = new sst.aws.OpenSearch("MySearch");
```

Once linked to a function, we can connect to it.

```
import { Resource } from "sst";

import { Client } from "@opensearch-project/opensearch";

const client = new Client({

node: Resource.MySearch.url,

auth: {

username: Resource.MySearch.username,

password: Resource.MySearch.password

}

});
```

This is using the [OpenSearch JS SDK](https://docs.opensearch.org/docs/latest/clients/javascript/index) to connect to the OpenSearch domain..

```
const search = new sst.aws.OpenSearch("MySearch");

const app = new sst.aws.Function("MyApp", {

handler: "index.handler",

url: true,

link: [search],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-open-search).

* * *

## [AWS PlanetScale Drizzle MySQL](https://sst.dev/docs/examples/\#aws-planetscale-drizzle-mysql)

In this example, we use PlanetScale with a branch-per-stage pattern. Every stage gets its own
database branch — so each PR can have an isolated database.

```
const db = planetscale.getDatabaseVitessOutput({

id: "mydb",

organization: "myorg",

});

const branch =

$app.stage === "production"

? planetscale.getVitessBranchOutput({

id: db.defaultBranch,

organization: db.organization,

database: db.name,

})

: new planetscale.VitessBranch("DatabaseBranch", {

database: db.name,

organization: db.organization,

name: $app.stage,

parentBranch: db.defaultBranch,

});
```

We then create a password and wrap it in a `Linkable` to link it to a function.

```
new sst.aws.Function("Api", {

handler: "src/api.handler",

link: [database],

url: true,

});
```

You can push your Drizzle schema changes to PlanetScale with:

```
bun run db:push
```

In the function we use [Drizzle ORM](https://orm.drizzle.team/) with the
[`Resource`](https://sst.dev/docs/reference/sdk/#resource) helper.

```
import { drizzle } from "drizzle-orm/planetscale-serverless";

import { Resource } from "sst";

export const db = drizzle({

connection: {

host: Resource.Database.host,

username: Resource.Database.username,

password: Resource.Database.password,

},

});
```

```
const db = planetscale.getDatabaseVitessOutput({

id: "example",

organization: "vimtor",

});

const branch =

$app.stage === "production"

? planetscale.getVitessBranchOutput({

id: db.defaultBranch,

organization: db.organization,

database: db.name,

})

: new planetscale.VitessBranch("DatabaseBranch", {

database: db.name,

organization: db.organization,

name: $app.stage,

parentBranch: db.defaultBranch,

});

const password = new planetscale.VitessBranchPassword("DatabasePassword", {

database: db.name,

organization: db.organization,

branch: branch.name,

role: "admin",

name: `${$app.name}-${$app.stage}`,

});

const database = new sst.Linkable("Database", {

properties: {

host: password.accessHostUrl,

username: password.username,

password: password.plainText,

database: db.name,

port: 3306,

},

});

const api = new sst.aws.Function("Api", {

handler: "src/api.handler",

link: [database],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-planetscale-drizzle-mysql).

* * *

## [AWS PlanetScale Drizzle Postgres](https://sst.dev/docs/examples/\#aws-planetscale-drizzle-postgres)

In this example, we use PlanetScale Postgres with a branch-per-stage pattern. Every stage
gets its own database branch — so each PR can have an isolated database.

```
const db = planetscale.getDatabasePostgresOutput({

id: "mydb",

organization: "myorg",

});

const branch =

$app.stage === "production"

? planetscale.getPostgresBranchOutput({

id: db.defaultBranch,

organization: db.organization,

database: db.name,

})

: new planetscale.PostgresBranch("DatabaseBranch", {

database: db.name,

organization: db.organization,

name: $app.stage,

parentBranch: db.defaultBranch,

});
```

We then create a role and wrap it in a `Linkable` to link it to a function.

```
new sst.aws.Function("Api", {

handler: "src/api.handler",

link: [database],

url: true,

});
```

You can push your Drizzle schema changes to PlanetScale with:

```
bun run db:push
```

In the function we use [Drizzle ORM](https://orm.drizzle.team/) with the
[`Resource`](https://sst.dev/docs/reference/sdk/#resource) helper.

```
import { drizzle } from "drizzle-orm/postgres-js";

import { Resource } from "sst";

import postgres from "postgres";

const client = postgres({

host: Resource.Database.host,

username: Resource.Database.username,

password: Resource.Database.password,

database: Resource.Database.database,

});

export const db = drizzle(client);
```

```
const db = planetscale.getDatabasePostgresOutput({

id: "mydb",

organization: "myorg",

});

const branch =

$app.stage === "production"

? planetscale.getPostgresBranchOutput({

id: db.defaultBranch,

organization: db.organization,

database: db.name,

})

: new planetscale.PostgresBranch("DatabaseBranch", {

database: db.name,

organization: db.organization,

name: $app.stage,

parentBranch: db.defaultBranch,

});

const role = new planetscale.PostgresBranchRole("DatabaseRole", {

database: db.name,

organization: db.organization,

branch: branch.name,

name: `${$app.name}-${$app.stage}`,

inheritedRoles: [\
\
    "pg_read_all_data",\
\
    "pg_write_all_data",\
\
    "postgres", // Only needed for pushing schema changes\
\
],

});

const database = new sst.Linkable("Database", {

properties: {

host: role.accessHostUrl,

username: role.username,

password: role.password,

database: role.databaseName,

port: 6432, // Use 5432 for direct connection instead of PgBouncer

},

});

const api = new sst.aws.Function("Api", {

handler: "src/api.handler",

link: [database],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-planetscale-drizzle-postgres).

* * *

## [Policy Pack Validation](https://sst.dev/docs/examples/\#policy-pack-validation)

You can use Pulumi Policy Packs to enforce compliance and security policies on your
infrastructure before deployment. Created policies with and enforcement level of “mandatory” will block the deployment.

This example shows how to use the `--policy` flag with `sst diff` and `sst deploy` to
validate your infrastructure against a policy pack.

Run the diff command with a policy pack to preview changes and check for violations:

```
sst diff --policy ./policy-pack --stage prod
```

Deploy with policy validation:

```
sst deploy --policy ./policy-pack --stage prod
```

To get started you can create a new policy pack for AWS using:

```
mkdir policy-pack

cd policy-pack

pulumi policy new aws-typescript
```

The example policy pack (check the full example) enforces that all IAM roles must have a permission boundary, blocking the deployment in this sst example.

```
const role = new aws.iam.Role("ExampleRoleWithBoundary", {

assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({

Service: "lambda.amazonaws.com",

}),

// To make this compliant with the policy example, uncomment the following line:

// permissionsBoundary: "arn:aws:iam::aws:policy/PowerUserAccess",

});

new aws.iam.RolePolicy("S3GetItemPolicy", {

role: role.id,

policy: aws.iam.getPolicyDocumentOutput({

statements: [\
\
      {\
\
        actions: ["s3:GetObject"],\
\
        resources: ["*"],\
\
      },\
\
    ],

}).json,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-policy-pack).

* * *

## [AWS Postgres local](https://sst.dev/docs/examples/\#aws-postgres-local)

In this example, we connect to a locally running Postgres instance for dev. While
on deploy, we use RDS.

```
docker run \

--rm \

-p 5432:5432 \

-v $(pwd)/.sst/storage/postgres:/var/lib/postgresql/data \

-e POSTGRES_USER=postgres \

-e POSTGRES_PASSWORD=password \

-e POSTGRES_DB=local \

postgres:16.4
```

The data is saved to the `.sst/storage` directory. So if you restart the dev server, the
data will still be there.

We then configure the `dev` property of the `Postgres` component with the settings for the
local Postgres instance.

```
dev: {

username: "postgres",

password: "password",

database: "local",

port: 5432,

}
```

By providing the `dev` prop for Postgres, SST will use the local Postgres instance and
not deploy a new RDS database when running `sst dev`.

It also allows us to access the database through a Resource `link` without having to
conditionally check if we are running locally.

```
const pool = new Pool({

host: Resource.MyPostgres.host,

port: Resource.MyPostgres.port,

user: Resource.MyPostgres.username,

password: Resource.MyPostgres.password,

database: Resource.MyPostgres.database,

});
```

The above will work in both `sst dev` and `sst deploy`.

```
const vpc = new sst.aws.Vpc("MyVpc", { nat: "ec2" });

const rds = new sst.aws.Postgres("MyPostgres", {

dev: {

username: "postgres",

password: "password",

database: "local",

host: "localhost",

port: 5432,

},

vpc,

});

new sst.aws.Function("MyFunction", {

vpc,

url: true,

link: [rds],

handler: "index.handler",

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-postgres-local).

* * *

## [Prisma in Lambda](https://sst.dev/docs/examples/\#prisma-in-lambda)

To use Prisma in a Lambda function you need to

- Generate the Prisma Client with the right architecture
- Copy the generated client to the function
- Run the function inside a VPC

You can set the architecture using the `binaryTargets` option in `prisma/schema.prisma`.

```
// For x86

binaryTargets = ["native", "rhel-openssl-3.0.x"]

// For ARM

// binaryTargets = ["native", "linux-arm64-openssl-3.0.x"]
```

You can also switch to ARM, just make sure to also change the function architecture in your
`sst.config.ts`.

```
{

// For ARM

architecture: "arm64"

}
```

To generate the client, you need to run `prisma generate` when you make changes to the
schema.

Since this [needs to be done on every deploy](https://www.prisma.io/docs/orm/more/help-and-troubleshooting/help-articles/vercel-caching-issue#a-custom-postinstall-script), we add a `postinstall` script to the `package.json`.

```
"scripts": {

"postinstall": "prisma generate"

}
```

This runs the command on `npm install`.

We then need to copy the generated client to the function when we deploy.

```
{

copyFiles: [{ from: "node_modules/.prisma/client/" }]

}
```

Our function also needs to run inside a VPC, since Prisma doesn’t support the Data API.

```
{

vpc

}
```

#### [Prisma in serverless environments](https://sst.dev/docs/examples/\#prisma-in-serverless-environments)

Prisma is [not great in serverless environments](https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/databases-connections#serverless-environments-faas). For a couple of reasons:

1. It doesn’t support Data API, so you need to manage the connection pool on your own.
2. Without the Data API, your functions need to run inside a VPC.
   - You cannot use `sst dev` without [connecting to the VPC](https://sst.dev/docs/live#using-a-vpc).
3. Due to the internal architecture of their client, it’s also has slower cold starts.

Instead we recommend using [Drizzle](https://orm.drizzle.team/). This example is here for
reference for people that are already using Prisma.

```
const vpc = new sst.aws.Vpc("MyVpc", { nat: "managed" });

const rds = new sst.aws.Postgres("MyPostgres", { vpc });

const api = new sst.aws.Function("MyApi", {

vpc,

url: true,

link: [rds],

// For ARM

// architecture: "arm64",

handler: "index.handler",

copyFiles: [{ from: "node_modules/.prisma/client/" }],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-prisma-lambda).

* * *

## [Puppeteer in Lambda](https://sst.dev/docs/examples/\#puppeteer-in-lambda)

To use Puppeteer in a Lambda function you need:

1. [`puppeteer-core`](https://www.npmjs.com/package/puppeteer-core)
2. Chromium
   - In `sst dev`, we’ll use a locally installed Chromium version.
   - In `sst deploy`, we’ll use the [`@sparticuz/chromium`](https://github.com/sparticuz/chromium) package. It comes with a pre-built binary for Lambda.

#### [Chromium version](https://sst.dev/docs/examples/\#chromium-version)

Since Puppeteer has a preferred version of Chromium, we’ll need to check the version of
Chrome that a given version of Puppeteer supports. Head over to the
[Puppeteer’s Chromium Support page](https://pptr.dev/chromium-support) and check which
versions work together.

For example, Puppeteer v23.1.1 supports Chrome for Testing 127.0.6533.119. So, we’ll use the
v127 of `@sparticuz/chromium`.

```
npm install puppeteer-core@23.1.1 @sparticuz/chromium@127.0.0
```

#### [Install Chromium locally](https://sst.dev/docs/examples/\#install-chromium-locally)

To use this locally, you’ll need to install Chromium.

```
npx @puppeteer/browsers install chromium@latest --path /tmp/localChromium
```

Once installed you’ll see the location of the Chromium binary, `/tmp/localChromium/chromium/mac_arm-1350406/chrome-mac/Chromium.app/Contents/MacOS/Chromium`.

Update this in your Lambda function.

```
// This is the path to the local Chromium binary

const YOUR_LOCAL_CHROMIUM_PATH = "/tmp/localChromium/chromium/mac_arm-1350406/chrome-mac/Chromium.app/Contents/MacOS/Chromium";
```

You’ll notice we are using the right binary with the `SST_DEV` environment variable.

```
const browser = await puppeteer.launch({

args: chromium.args,

defaultViewport: chromium.defaultViewport,

executablePath: process.env.SST_DEV

? YOUR_LOCAL_CHROMIUM_PATH

: await chromium.executablePath(),

headless: chromium.headless,

});
```

#### [Deploy](https://sst.dev/docs/examples/\#deploy)

We don’t need a layer to deploy this because `@sparticuz/chromium` comes with a pre-built
binary for Lambda.

We just need to set it in the [`nodejs.install`](https://sst.dev/docs/component/aws/function#nodejs-install).

```
{

nodejs: {

install: ["@sparticuz/chromium"]

}

}
```

And on deploy, SST will use the right binary.

We are giving our function more memory and a longer timeout since running Puppeteer can
take a while.

```
const api = new sst.aws.Function("MyFunction", {

url: true,

memory: "2 GB",

timeout: "15 minutes",

handler: "index.handler",

nodejs: {

install: ["@sparticuz/chromium"],

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-puppeteer).

* * *

## [AWS Lambda Python container](https://sst.dev/docs/examples/\#aws-lambda-python-container)

Python Lambda function that use large dependencies like `numpy` and `pandas`, can
hit the 250MB Lambda package limit. To work around this, you can deploy them
as a container image to Lambda.

In this example, we deploy two functions as container image.

```
const base = new sst.aws.Function("PythonFn", {

python: {

container: true,

},

handler: "./functions/src/functions/api.handler",

runtime: "python3.11",

link: [linkableValue],

url: true,

});
```

Now when you run `sst deploy`, it uses a built-in Dockerfile to build the image
and deploy it. You’ll need to have the Docker daemon running.

To use a custom Dockerfile, you can place a `Dockerfile` in the root of the
uv workspace for your function.

```
const custom = new sst.aws.Function("PythonFnCustom", {

python: {

container: true,

},

handler: "./custom_dockerfile/src/custom_dockerfile/api.handler",

runtime: "python3.11",

link: [linkableValue],

url: true,

});
```

Here we have a `Dockerfile` in the `custom_dockerfile/` directory.

```
# The python version to use is supplied as an arg from SST

ARG PYTHON_VERSION=3.11

# Use an official AWS Lambda base image for Python

FROM public.ecr.aws/lambda/python:${PYTHON_VERSION}

# ...
```

The project structure looks something like this.

```
├── sst.config.ts

├── pyproject.toml

└── custom_dockerfile

├── pyproject.toml

├── Dockerfile

└── src

└── custom_dockerfile

└── api.py
```

Locally, you want to set the Python version in your `pyproject.toml` to make sure
that `sst dev` uses the same version as `sst deploy`.

```
const linkableValue = new sst.Linkable("MyLinkableValue", {

properties: {

foo: "Hello World",

},

});

const base = new sst.aws.Function("PythonFn", {

python: {

container: true,

},

handler: "./functions/src/functions/api.handler",

runtime: "python3.11",

link: [linkableValue],

url: true,

});

const custom = new sst.aws.Function("PythonFnCustom", {

python: {

container: true,

},

handler: "./custom_dockerfile/src/custom_dockerfile/api.handler",

runtime: "python3.11",

link: [linkableValue],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-python-container).

* * *

## [AWS Lambda Python Hugging Face](https://sst.dev/docs/examples/\#aws-lambda-python-hugging-face)

Uses a Python Lambda container image to deploy a lightweight
[Hugging Face](https://huggingface.co/) model.

Uses the [transformers](https://github.com/huggingface/transformers) library to
generate text using the
[TinyStories-33M](https://huggingface.co/roneneldan/TinyStories-33M) model. The
backend is the pytorch cpu runtime.

This example also shows how it is possible to use custom index resolution to get
dependencies from a private pypi server such as the pytorch cpu link. This
example also shows how to use a custom Dockerfile to handle complex builds such
as installing pytorch and pruning the build size.

```
new sst.aws.Function("PythonFunction", {

python: {

container: true,

},

handler: "functions/src/functions/api.handler",

runtime: "python3.12",

memory: "2048 MB",

timeout: "120 seconds",

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-python-huggingface).

* * *

## [AWS Lambda Python](https://sst.dev/docs/examples/\#aws-lambda-python)

SST uses [uv](https://docs.astral.sh/uv/) to manage your Python runtime, make
sure you have it [installed](https://docs.astral.sh/uv/getting-started/installation/).

Any [uv workspace](https://docs.astral.sh/uv/concepts/projects/workspaces/#workspace-sources)
package can be built and deployed as a Lambda function using SST. Drop-in mode
is currently not supported.

In this example we deploy a handler from the `functions/` directory. It depends
on shared code from another uv workspace in the `core/` directory.

```
├── sst.config.ts

├── pyproject.toml

├── core

│   ├── pyproject.toml

│   └── src

│       └── core

│           └── __init__.py

└── functions

├── pyproject.toml

└── src

└── functions

├── __init__.py

└── api.py
```

The `handler` is the path to the handler file and the name of the handler function
in it.

```
new sst.aws.Function("PythonFunction", {

handler: "functions/src/functions/api.handler",

runtime: "python3.11",

link: [linkableValue],

url: true,

});
```

SST will traverse up from the handler path and look for the nearest
`pyproject.toml`. And will throw an error if it can’t find one.

To access linked resources, you can use the SST SDK.

```
from sst import Resource

def handler(event, context):

print(Resource.MyLinkableValue.foo)
```

Where the `sst-sdk` package can be added to your `pyproject.toml`.

```
[project]

dependencies = ["sst-sdk"]

[tool.uv.sources]

sst-sdk = { git = "https://github.com/anomalyco/sst.git", subdirectory = "sdk/python", branch = "dev" }
```

You also want to set the Python version in your `pyproject.toml` to the same
version as the one in Lambda.

```
requires-python = "==3.11.*"
```

This makes sure that your functions work the same in `sst dev` as `sst deploy`.

```
const linkableValue = new sst.Linkable("MyLinkableValue", {

properties: {

foo: "Hello World",

},

});

new sst.aws.Function("PythonFunction", {

handler: "functions/src/functions/api.handler",

runtime: "python3.11",

link: [linkableValue],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-python).

* * *

## [Subscribe to queues](https://sst.dev/docs/examples/\#subscribe-to-queues)

Create an SQS queue, subscribe to it, and publish to it from a function.

```
const queue = new sst.aws.Queue("MyQueue");

queue.subscribe("subscriber.handler");

const app = new sst.aws.Function("MyApp", {

handler: "publisher.handler",

link: [queue],

url: true,

});
```

The subscriber will read messages from the queue in batches. This array of messages exists on the `Records` property of the `SQSEvent`.

```
import type { SQSEvent, SQSHandler } from "aws-lambda";

export const handler: SQSHandler = async (event: SQSEvent) => {

for (const record of event.Records){

// Message bodies are always strings

console.log(record.body)

}

return;

};
```

By default, all messages in the batch become visible in the queue again if an error occurs. This can lead to unnecessary extra processing and messages being processed more than once. The solution is to enable [partial batch responsese](https://docs.aws.amazon.com/lambda/latest/dg/services-sqs-errorhandling.html#services-sqs-batchfailurereporting) and return which specific messages within the batch should be made visible again in the queue.

Update the queue subscriber.

```
queue.subscribe("subscriber.handler", {

batch: {

partialResponses: true,

}

});
```

Then update the handler to return the failed items.

```
import type { SQSEvent, SQSHandler } from "aws-lambda";

export const handler: SQSHandler = async (event: SQSEvent) => {

const batchItemFailures = []

for (const record of event.Records){

try {

console.log(record.body)

if (Math.random() < 0.1){

throw new Error("An error occurred")

}

}

catch (e) {

batchItemFailures.push({ itemIdentifier: record.messageId });

}

}

// Failed items will be made visible in the queue again

return { batchItemFailures };

};
```

```
const queue = new sst.aws.Queue("MyQueue");

queue.subscribe("subscriber.handler", {

batch: {

partialResponses: true,

}

});

const app = new sst.aws.Function("MyApp", {

handler: "publisher.handler",

link: [queue],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-queue).

* * *

## [AWS Quota Increase](https://sst.dev/docs/examples/\#aws-quota-increase)

Use the Pulumi AWS provider to request an increase to an AWS service quota.
In this example, we increase the Lambda concurrent executions quota.

You can find service and quota codes in the
[AWS Service Quotas console](https://console.aws.amazon.com/servicequotas) or by running
`aws service-quotas list-service-quotas --service-code <service>`.

```
new aws.servicequotas.ServiceQuota("LambdaConcurrentExecutions", {

serviceCode: "lambda",

quotaCode: "L-B99A9384",

value: 2000,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-quota-increase).

* * *

## [Rails container](https://sst.dev/docs/examples/\#rails-container)

Deploy a Ruby on Rails app in a container with a linked public S3 bucket.

```
const bucket = new sst.aws.Bucket("MyBucket", {

access: "public",

});

const vpc = new sst.aws.Vpc("MyVpc");

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

new sst.aws.Service("MyService", {

cluster,

loadBalancer: {

ports: [{ listen: "80/http", forward: "3000/http" }],

},

environment: {

RAILS_MASTER_KEY: (await import("fs")).readFileSync(

"config/master.key",

"utf8"

),

},

dev: {

command: "bin/rails server",

},

link: [bucket],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-rails).

* * *

## [AWS RDS MySQL public](https://sst.dev/docs/examples/\#aws-rds-mysql-public)

Create a publicly accessible MySQL RDS instance with a security group that
allows external connections.

```
const MYSQL_PORT = 3306;

const ALL_IPS = '0.0.0.0/0';

const publicSecurityGroup = new aws.ec2.SecurityGroup(

'MyPublicSecurityGroup',

{

ingress: [\
\
      {\
\
        // Expose to public connection. Remove if not needed\
\
        protocol: 'tcp',\
\
        fromPort: MYSQL_PORT,\
\
        toPort: MYSQL_PORT,\
\
        cidrBlocks: [ALL_IPS],\
\
      },\
\
    ],

},

);

const identifier = 'my-db-instance';

const database = new aws.rds.Instance(

'MyDbInstanceMySQL',

{

identifier,

engine: 'mysql',

// free-tier

instanceClass: 'db.t3.micro',

allocatedStorage: 20, // free-tier 20GB

// credentials

username: 'dev-user',

password: 'dev-password',

dbName: 'dev-database',

// settings

tags: { Name: identifier },

skipFinalSnapshot: true,

// allow public access

vpcSecurityGroupIds: [publicSecurityGroup.id],

publiclyAccessible: true,

},

);
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-rds-instance-mysql-public).

* * *

## [AWS Redis local](https://sst.dev/docs/examples/\#aws-redis-local)

In this example, we connect to a local Docker Redis instance for dev. While on deploy, we use
Redis ElastiCache.

We use the [`docker run`](https://docs.docker.com/reference/cli/docker/container/run/) CLI
to start a local Redis server. You don’t have to use Docker, you can run it locally any way
you want.

```
docker run \

--rm \

-p 6379:6379 \

-v $(pwd)/.sst/storage/redis:/data \

redis:latest
```

The data is persisted to the `.sst/storage` directory. So if you restart the dev server,
the data will still be there.

We then configure the `dev` property of the `Redis` component with the settings for the
local Redis server.

```
dev: {

host: "localhost",

port: 6379

}
```

By providing the `dev` prop for Redis, SST will use the local Redis server and
not deploy a new Redis ElastiCache cluster when running `sst dev`.

It also allows us to access Redis through a Resource `link`.

```
const client = Resource.MyRedis.host === "localhost"

? new Redis({

host: Resource.MyRedis.host,

port: Resource.MyRedis.port,

})

: new Cluster(

[{\
\
        host: Resource.MyRedis.host,\
\
        port: Resource.MyRedis.port,\
\
      }],

{

redisOptions: {

tls: { checkServerIdentity: () => undefined },

username: Resource.MyRedis.username,

password: Resource.MyRedis.password,

},

},

);
```

The local Redis server is running in `standalone` mode, whereas on deploy it’ll be in
`cluster` mode. So our Lambda function needs to connect using the right config.

```
const vpc = new sst.aws.Vpc("MyVpc", { nat: "managed" });

const redis = new sst.aws.Redis("MyRedis", {

dev: {

host: "localhost",

port: 6379,

},

vpc,

});

new sst.aws.Function("MyApp", {

vpc,

url: true,

link: [redis],

handler: "index.handler",

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-redis-local).

* * *

## [AWS Remix container with Redis](https://sst.dev/docs/examples/\#aws-remix-container-with-redis)

Creates a hit counter app with Remix and Redis.

This deploys Remix as a Fargate service to ECS and it’s linked to Redis.

```
new sst.aws.Service("MyService", {

cluster,

link: [redis],

loadBalancer: {

ports: [{ listen: "80/http", forward: "3000/http" }],

},

dev: {

command: "npm run dev",

},

});
```

Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local
machine.

```
sudo npx sst tunnel install
```

This needs _sudo_ to create a network interface on your machine. You’ll only need to do this
once on your machine.

To start your app locally run.

```
npx sst dev
```

Now if you go to `http://localhost:5173` you’ll see a counter update as you refresh the page.

Finally, you can deploy it by adding the `Dockerfile` that’s included in this example and
running `npx sst deploy --stage production`.

```
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });

const redis = new sst.aws.Redis("MyRedis", { vpc });

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

new sst.aws.Service("MyService", {

cluster,

link: [redis],

loadBalancer: {

ports: [{ listen: "80/http", forward: "3000/http" }],

},

dev: {

command: "npm run dev",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-remix-redis).

* * *

## [AWS Remix streaming](https://sst.dev/docs/examples/\#aws-remix-streaming)

Follows the [Remix Streaming](https://remix.run/docs/en/main/guides/streaming) guide to create
an app that streams data.

Uses the `defer` utility to stream data through the `loader` function.

```
return defer({

spongebob,

friends: friendsPromise,

});
```

Then uses the the `Suspense` and `Await` components to render the data.

```
<Suspense fallback={<div>Loading...</div>}>

<Await resolve={friends}>

{ /* ... */ }

</Await>

</Suspense>
```

You should see the _friends_ section load after a 3 second delay.

Streaming works out of the box with the `Remix` component.

```
new sst.aws.Remix("MyWeb");
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-remix-stream).

* * *

## [Router and bucket](https://sst.dev/docs/examples/\#router-and-bucket)

Creates a router that serves static files from the `public` folder of a given bucket.

```
// Create a bucket that CloudFront can access

const bucket = new sst.aws.Bucket("MyBucket", {

access: "cloudfront",

});

// Upload the image to the `public` folder

new aws.s3.BucketObjectv2("MyImage", {

bucket: bucket.name,

key: "public/spongebob.svg",

contentType: "image/svg+xml",

source: $asset("spongebob.svg"),

});

const router = new sst.aws.Router("MyRouter", {

routes: {

"/*": {

bucket,

rewrite: { regex: "^/(.*)$", to: "/public/$1" },

},

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-router-bucket).

* * *

## [Router protection with OAC](https://sst.dev/docs/examples/\#router-protection-with-oac)

Creates a router with Origin Access Control (OAC) to secure Lambda function URLs
behind CloudFront. Direct access to the Lambda URL returns 403.

```
const router = new sst.aws.Router("MyRouter", {

protection: "oac",

});

const api = new sst.aws.Function("MyApi", {

handler: "api.handler",

url: {

router: { instance: router, path: "/api" },

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-router-protection).

* * *

## [Router with WAF](https://sst.dev/docs/examples/\#router-with-waf)

Enable WAF (Web Application Firewall) for a Router to protect against common
web exploits and bots.

WAF includes rate limiting per IP, and AWS managed rules for core rule set,
known bad inputs, and SQL injection protection.

You can also enable WAF logging to CloudWatch to monitor requests.

```
const api = new sst.aws.Function("MyApi", {

handler: "api.handler",

url: true,

});

const router = new sst.aws.Router("MyRouter", {

routes: {

"/*": api.url,

},

waf: {

rateLimitPerIp: 1000,

managedRules: {

coreRuleSet: true,

knownBadInputs: true,

sqlInjection: true,

},

logging: true,

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-router-waf).

* * *

## [Router and function URL](https://sst.dev/docs/examples/\#router-and-function-url)

Creates a router that routes all requests to a function with a URL.

```
const api = new sst.aws.Function("MyApi", {

handler: "api.handler",

url: true,

});

const bucket = new sst.aws.Bucket("MyBucket", {

access: "public",

});

const router = new sst.aws.Router("MyRouter", {

routes: {

"/api/*": api.url,

"/*": $interpolate`https://${bucket.domain}`,

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-router).

* * *

## [Rust function](https://sst.dev/docs/examples/\#rust-function)

Deploy a Rust Lambda function with a function URL and a linked S3 bucket.

```
const bucket = new sst.aws.Bucket("Bucket");

const lambda = new sst.aws.Function("RustFunction", {

runtime: "rust",

handler: "./",

url: true,

architecture: "arm64",

link: [bucket],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-rust-api).

* * *

## [Rust container](https://sst.dev/docs/examples/\#rust-container)

Deploy a Rust app in a container with a load balancer using a Dockerfile.

```
const vpc = new sst.aws.Vpc("MyVpc", { nat: "gateway" });

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

const service = new sst.aws.Service("MyService", {

cluster,

image: {

context: "./",

dockerfile: "Dockerfile",

},

loadBalancer: {

domain: "rust.dockerfile.dev.sst.dev",

ports: [\
\
      { listen: "80/http" },\
\
      { listen: "443/https", forward: "80/http" },\
\
    ],

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-rust-cluster).

* * *

## [Rust Loco](https://sst.dev/docs/examples/\#rust-loco)

Deploy a Rust Loco app with a Postgres database, Redis, and a background worker
service.

```
const vpc = new sst.aws.Vpc("LocoVpc", {

bastion: true,

});

const database = new sst.aws.Postgres("LocoDatabase", { vpc });

const redis = new sst.aws.Redis("LocoRedis", { vpc });

const DATABASE_URL = $interpolate`postgres://${

database.username

}:${database.password.apply(encodeURIComponent)}@${database.host}:${

database.port

}/${database.database}`;

const REDIS_URL = $interpolate`redis://${

redis.username

}:${redis.password.apply(encodeURIComponent)}@${redis.host}:${redis.port}`;

const locoCluster = new sst.aws.Cluster("LocoCluster", { vpc });

// external facing http service

const locoServer = new sst.aws.Service("LocoApp", {

cluster: locoCluster,

architecture: "x86_64",

scaling: { min: 2, max: 4 },

command: ["start"],

loadBalancer: {

ports: [{ listen: "80/http", forward: "5150/http" }],

},

environment: {

DATABASE_URL,

REDIS_URL,

},

link: [database, redis],

dev: {

command: "cargo loco start",

},

});

// add a worker that uses redis to process jobs off a queue

new sst.aws.Service("LocoWorker", {

cluster: locoCluster,

architecture: "x86_64",

command: ["start", "--worker"],

environment: {

DATABASE_URL,

REDIS_URL,

},

link: [database, redis],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-rust-loco).

* * *

## [AWS Cluster Service Discovery](https://sst.dev/docs/examples/\#aws-cluster-service-discovery)

In this example, we are connecting to a service running on a cluster using its AWS Cloud
Map service host name. This is useful for service discovery.

We are deploying a service to a cluster in a VPC. And we can access it within the VPC using
the service’s cloud map hostname.

```
const response = await fetch(`http://${Resource.MyService.service}`);
```

Here we are accessing it through a Lambda function that’s linked to the service and is
deployed to the same VPC.

```
const vpc = new sst.aws.Vpc("MyVpc", { nat: "ec2" });

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

const service = new sst.aws.Service("MyService", { cluster });

new sst.aws.Function("MyFunction", {

vpc,

url: true,

link: [service],

handler: "lambda.handler",

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-service-discovery).

* * *

## [AWS Shared ALB](https://sst.dev/docs/examples/\#aws-shared-alb)

Creates a standalone ALB that is shared across stages. In dev, the ALB is
referenced via `Alb.get()`. In production, it’s created fresh.

Uses the `$dev ? get : new` pattern to share infrastructure across stages.

```
const vpc = $dev

? sst.aws.Vpc.get("MyVpc", "vpc-xxx")

: new sst.aws.Vpc("MyVpc");

const cluster = $dev

? sst.aws.Cluster.get("MyCluster", { id: "cluster-xxx", vpc })

: new sst.aws.Cluster("MyCluster", { vpc });

const alb = $dev

? sst.aws.Alb.get("SharedAlb", "arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/xxx")

: new sst.aws.Alb("SharedAlb", {

vpc,

listeners: [\
\
        { port: 80, protocol: "http" },\
\
      ],

});

if ($dev) {

new sst.aws.Service("Web", {

cluster,

image: { context: "web/" },

loadBalancer: {

instance: alb,

rules: [\
\
        {\
\
          listen: "80/http",\
\
          forward: "3000/http",\
\
          conditions: { path: "/app/*" },\
\
          priority: 200,\
\
        },\
\
      ],

},

});

}

new sst.aws.Service("Api", {

cluster,

image: { context: "api/" },

loadBalancer: {

instance: alb,

rules: [\
\
      {\
\
        listen: "80/http",\
\
        forward: "3000/http",\
\
        conditions: { path: "/api/*" },\
\
        priority: 100,\
\
      },\
\
    ],

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-shared-alb-static).

* * *

## [AWS Shared ALB](https://sst.dev/docs/examples/\#aws-shared-alb-1)

Creates a standalone ALB shared across multiple services.
Shows advanced routing with path conditions, header conditions, and health checks.

```
const alb = new sst.aws.Alb("SharedAlb", {

vpc,

listeners: [\
\
    { port: 80, protocol: "http" },\
\
],

});
```

Services can use header-based routing in addition to path-based:

```
new sst.aws.Service("InternalApi", {

cluster,

image: { context: "api/" },

loadBalancer: {

instance: alb,

rules: [\
\
      {\
\
        listen: "80/http",\
\
        forward: "3000/http",\
\
        conditions: {\
\
          path: "/api/*",\
\
          header: { name: "x-internal", values: ["true"] },\
\
        },\
\
        priority: 50,\
\
      },\
\
    ],

},

});
```

This example creates:

- A shared ALB with an HTTP listener
- An API service with path-based routing and custom health check
- A Web service with path-based routing
- Both services share the same ALB

```
const vpc = new sst.aws.Vpc("MyVpc");

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

// Create a shared ALB with an HTTP listener

const alb = new sst.aws.Alb("SharedAlb", {

vpc,

listeners: [{ port: 80, protocol: "http" }],

});

// API service — handles /api/* with a custom health check path

new sst.aws.Service("Api", {

cluster,

image: { context: "api/" },

loadBalancer: {

instance: alb,

rules: [\
\
      {\
\
        listen: "80/http",\
\
        forward: "3000/http",\
\
        conditions: { path: "/api/*" },\
\
        priority: 100,\
\
      },\
\
    ],

health: {

"3000/http": {

path: "/api/health",

interval: "10 seconds",

timeout: "5 seconds",

healthyThreshold: 2,

unhealthyThreshold: 3,

},

},

},

});

// Web service — handles everything else under /app/*

new sst.aws.Service("Web", {

cluster,

image: { context: "web/" },

loadBalancer: {

instance: alb,

rules: [\
\
      {\
\
        listen: "80/http",\
\
        forward: "3000/http",\
\
        conditions: { path: "/app/*" },\
\
        priority: 200,\
\
      },\
\
    ],

health: {

"3000/http": {

path: "/app/health",

interval: "10 seconds",

timeout: "5 seconds",

},

},

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-shared-alb).

* * *

## [Sharp in Lambda](https://sst.dev/docs/examples/\#sharp-in-lambda)

Uses the [Sharp](https://sharp.pixelplumbing.com/) library to resize images. In this example,
it resizes a `logo.png` local file to 100x100 pixels.

```
{

nodejs: { install: ["sharp"] }

}
```

We don’t need a layer to deploy this because `sharp` comes with a pre-built binary for Lambda.
This is handled by [`nodejs.install`](https://sst.dev/docs/component/aws/function#nodejs-install).

In dev, this uses the sharp npm package locally.

```
{

"dependencies": {

"sharp": "^0.33.5"

}

}
```

On deploy, SST will use the right binary from the sharp package for the target Lambda
architecture.

```
const func = new sst.aws.Function("MyFunction", {

url: true,

handler: "index.handler",

nodejs: { install: ["sharp"] },

copyFiles: [{ from: "logo.png" }],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-sharp).

* * *

## [AWS SolidStart WebSocket endpoint](https://sst.dev/docs/examples/\#aws-solidstart-websocket-endpoint)

Deploys a SolidStart app with a [WebSocket endpoint](https://docs.solidjs.com/solid-start/advanced/websocket)
in a container to AWS.

Uses the experimental WebSocket support in Nitro.

```
export default defineConfig({

server: {

experimental: {

websocket: true,

},

},

}).addRouter({

name: "ws",

type: "http",

handler: "./src/ws.ts",

target: "server",

base: "/ws",

});
```

Once deployed you can test the `/ws` endpoint and it’ll send a message back after a 3s delay.

```
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

new sst.aws.Service("MyService", {

cluster,

loadBalancer: {

ports: [{ listen: "80/http", forward: "3000/http" }],

},

dev: {

command: "npm run dev",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-solid-container-ws).

* * *

## [AWS static site basic auth](https://sst.dev/docs/examples/\#aws-static-site-basic-auth)

This deploys a simple static site and adds basic auth to it.

This is useful for dev environments where you want to share a static site with your team but
ensure that it’s not publicly accessible.

This works by injecting some code into a CloudFront function that checks the basic auth
header and matches it against the `USERNAME` and `PASSWORD` secrets.

```
{

injection: $interpolate`

if (

!event.request.headers.authorization

|| event.request.headers.authorization.value !== "Basic ${basicAuth}"

) {

return {

statusCode: 401,

headers: {

"www-authenticate": { value: "Basic" }

}

};

}`,

}
```

To deploy this, you need to first set the `USERNAME` and `PASSWORD` secrets.

```
sst secret set USERNAME my-username

sst secret set PASSWORD my-password
```

If you are deploying this to preview environments, you might want to set the secrets using
the [`--fallback`](https://sst.dev/docs/reference/cli#secret) flag.

```
const username = new sst.Secret("USERNAME");

const password = new sst.Secret("PASSWORD");

const basicAuth = $resolve([username.value, password.value]).apply(

([username, password]) =>

Buffer.from(`${username}:${password}`).toString("base64")

);

new sst.aws.StaticSite("MySite", {

path: "site",

// Don't password protect prod

edge: $app.stage !== "production"

? {

viewerRequest: {

injection: $interpolate`

if (

!event.request.headers.authorization

|| event.request.headers.authorization.value !== "Basic ${basicAuth}"

) {

return {

statusCode: 401,

headers: {

"www-authenticate": { value: "Basic" }

}

};

}`,

},

}

: undefined,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-static-site-basic-auth).

* * *

## [AWS static site](https://sst.dev/docs/examples/\#aws-static-site)

Deploy a simple HTML file as a static site with S3 and CloudFront. The website is stored in
the `site/` directory.

```
new sst.aws.StaticSite("MySite", {

path: "site",

errorPage: "404.html",

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-static-site).

* * *

## [AWS Step Functions task token](https://sst.dev/docs/examples/\#aws-step-functions-task-token)

Use Step Functions with task tokens to pause execution, send a message to an
SQS queue, and resume after processing.

```
// Create a queue the state machine will send messages to

const queue = new sst.aws.Queue("MyQueue");

// Define all the states of the state machine

const sendMessage = sst.aws.StepFunctions.sqsSendMessage({

name: "SendMessage",

integration: "token",

queue,

messageBody: {

// Task token passed in the message body

MyTaskToken: "{% $states.context.Task.Token %}",

},

});

const success = sst.aws.StepFunctions.succeed({ name: "Succeed" });

// Create the state machine

const stepFunction = new sst.aws.StepFunctions("MyStateMachine", {

definition: sendMessage.next(success),

});

// Create a function that will receive messages from the queue

queue.subscribe({

handler: "index.handler",

// Linking the state machine to grant permissions to call `SendTaskSuccess`

link: [stepFunction],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-step-functions-task-token).

* * *

## [AWS SvelteKit container with Redis](https://sst.dev/docs/examples/\#aws-sveltekit-container-with-redis)

Creates a hit counter app with SvelteKit and Redis.

This deploys SvelteKit as a Fargate service to ECS and it’s linked to Redis.

```
new sst.aws.Service("MyService", {

cluster,

link: [redis],

loadBalancer: {

ports: [{ listen: "80/http", forward: "3000/http" }],

},

dev: {

command: "npm run dev",

},

});
```

Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local
machine.

```
sudo npx sst tunnel install
```

This needs _sudo_ to create a network interface on your machine. You’ll only need to do this
once on your machine.

To start your app locally run.

```
npx sst dev
```

Now if you go to `http://localhost:5173` you’ll see a counter update as you refresh the page.

Finally, you can deploy it by adding the `Dockerfile` that’s included in this example and
running `npx sst deploy --stage production`.

```
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });

const redis = new sst.aws.Redis("MyRedis", { vpc });

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

new sst.aws.Service("MyService", {

cluster,

link: [redis],

loadBalancer: {

ports: [{ listen: "80/http", forward: "3000/http" }],

},

dev: {

command: "npm run dev",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-svelte-redis).

* * *

## [Swift in Lambda](https://sst.dev/docs/examples/\#swift-in-lambda)

Deploys a simple Swift application to Lambda using the `al2023` runtime.

Check out the README in the repo for more details.

```
const swift = new sst.aws.Function("Swift", {

runtime: "provided.al2023",

architecture: process.arch === "arm64" ? "arm64" : "x86_64",

bundle: build("app"),

handler: "bootstrap",

url: true,

});

const router = new sst.aws.Router("SwiftRouter", {

routes: {

"/*": swift.url,

},

domain: "swift.dev.sst.dev",

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-swift).

* * *

## [T3 Stack in AWS](https://sst.dev/docs/examples/\#t3-stack-in-aws)

Deploy [T3 stack](https://create.t3.gg/) with Drizzle and Postgres to AWS.

This example was created using `create-t3-app` and the following options: tRPC, Drizzle,
no auth, Tailwind, Postgres, and the App Router.

Instead of a local database, we’ll be using an RDS Postgres database.

```
const pool = new Pool({

host: Resource.MyPostgres.host,

port: Resource.MyPostgres.port,

user: Resource.MyPostgres.username,

password: Resource.MyPostgres.password,

database: Resource.MyPostgres.database,

});
```

Similarly, for Drizzle Kit.

```
export default {

schema: "./src/server/db/schema.ts",

dialect: "postgresql",

dbCredentials: {

ssl: {

rejectUnauthorized: false,

},

host: Resource.MyPostgres.host,

port: Resource.MyPostgres.port,

user: Resource.MyPostgres.username,

password: Resource.MyPostgres.password,

database: Resource.MyPostgres.database,

},

tablesFilter: ["aws-t3_*"],

} satisfies Config;
```

In our Next.js app we can access our Postgres database because we [link them](https://sst.dev/docs/linking/)
both. We don’t need to use our `.env` files.

```
const rds = new sst.aws.Postgres("MyPostgres", { vpc, proxy: true });

new sst.aws.Nextjs("MyWeb", {

vpc,

link: [rds]

});
```

To run this in dev mode run:

```
npm install

npx sst dev
```

It’ll take a few minutes to deploy the database and the VPC.

This also starts a tunnel to let your local machine connect to the RDS Postgres database.
Make sure you have it installed, you only need to do this once for your local machine.

```
sudo npx sst tunnel install
```

Now in a new terminal you can run the database migrations.

```
npm run db:push
```

We also have the Drizzle Studio start automatically in dev mode under the **Studio** tab.

```
new sst.x.DevCommand("Studio", {

link: [rds],

dev: {

command: "npx drizzle-kit studio",

},

});
```

And to make sure our credentials are available, we update our `package.json`
with the [`sst shell`](https://sst.dev/docs/reference/cli) CLI.

```
"db:generate": "sst shell drizzle-kit generate",

"db:migrate": "sst shell drizzle-kit migrate",

"db:push": "sst shell drizzle-kit push",

"db:studio": "sst shell drizzle-kit studio",
```

So running `npm run db:push` will run Drizzle Kit with the right credentials.

To deploy this to production run:

```
npx sst deploy --stage production
```

Then run the migrations.

```
npx sst shell --stage production npx drizzle-kit push
```

If you are running this locally, you’ll need to have a tunnel running.

```
npx sst tunnel --stage production
```

If you are doing this in a CI/CD pipeline, you’d want your build containers to be in the
same VPC.

```
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true, nat: "ec2" });

const rds = new sst.aws.Postgres("MyPostgres", { vpc, proxy: true });

new sst.aws.Nextjs("MyWeb", {

vpc,

link: [rds]

});

new sst.x.DevCommand("Studio", {

link: [rds],

dev: {

command: "npx drizzle-kit studio",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-t3).

* * *

## [AWS Task Cron](https://sst.dev/docs/examples/\#aws-task-cron)

Use the [`Task`](https://sst.dev/docs/component/aws/task) and [`Cron`](https://sst.dev/docs/component/aws/cron) components
for long running background tasks.

We have a node script that we want to run in `index.mjs`. It’ll be deployed as a
Docker container using `Dockerfile`.

It’ll be invoked by a cron job that runs every 2 minutes.

```
new sst.aws.Cron("MyCron", {

task,

schedule: "rate(2 minutes)"

});
```

When this is run in `sst dev`, the task is executed locally using `dev.command`.

```
dev: {

command: "node index.mjs"

}
```

To deploy, you need the Docker daemon running.

```
const bucket = new sst.aws.Bucket("MyBucket");

const vpc = new sst.aws.Vpc("MyVpc");

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

const task = new sst.aws.Task("MyTask", {

cluster,

link: [bucket],

dev: {

command: "node index.mjs",

},

});

new sst.aws.Cron("MyCron", {

task,

schedule: "rate(2 minutes)",

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-task-cron).

* * *

## [AWS Task](https://sst.dev/docs/examples/\#aws-task)

Use the [`Task`](https://sst.dev/docs/component/aws/task) component to run background tasks.

We have a node script that we want to run in `image/index.mjs`. It’ll be deployed as a
Docker container using `image/Dockerfile`.

We also have a function that the task is linked to. It uses the [SDK](https://sst.dev/docs/reference/sdk/)
to start the task.

```
import { Resource } from "sst";

import { task } from "sst/aws/task";

export const handler = async () => {

const ret = await task.run(Resource.MyTask);

return {

statusCode: 200,

body: JSON.stringify(ret, null, 2),

};

};
```

When this is run in `sst dev`, the task is executed locally using `dev.command`.

```
dev: {

command: "node index.mjs"

}
```

To deploy, you need the Docker daemon running.

```
const bucket = new sst.aws.Bucket("MyBucket");

const vpc = new sst.aws.Vpc("MyVpc", { nat: "ec2" });

const cluster = new sst.aws.Cluster("MyCluster", { vpc });

const task = new sst.aws.Task("MyTask", {

cluster,

public: true,

link: [bucket],

image: {

context: "image",

},

dev: {

command: "node index.mjs",

},

});

new sst.aws.Function("MyApp", {

vpc,

url: true,

link: [task],

handler: "index.handler",

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-task).

* * *

## [Subscribe to topics](https://sst.dev/docs/examples/\#subscribe-to-topics)

Create an SNS topic, publish to it from a function, and subscribe to it with a function and a queue.

```
const queue = new sst.aws.Queue("MyQueue");

queue.subscribe("subscriber.handler");

const topic = new sst.aws.SnsTopic("MyTopic");

topic.subscribe("MySubscriber1", "subscriber.handler", {});

topic.subscribeQueue("MySubscriber2", queue.arn);

const app = new sst.aws.Function("MyApp", {

handler: "publisher.handler",

link: [topic],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-topic).

* * *

## [Vector search](https://sst.dev/docs/examples/\#vector-search)

Store and search for vector data using the Vector component. Includes a seeder API that
uses an LLM to generate embeddings for some movies and optionally their posters.

Once seeded, you can call the search API to query the vector database.

```
const OpenAiApiKey = new sst.Secret("OpenAiApiKey");

const vector = new sst.aws.Vector("MyVectorDB", {

dimension: 1536,

});

const seeder = new sst.aws.Function("Seeder", {

handler: "index.seeder",

link: [OpenAiApiKey, vector],

copyFiles: [\
\
    { from: "iron-man.jpg", to: "iron-man.jpg" },\
\
    {\
\
      from: "black-widow.jpg",\
\
      to: "black-widow.jpg",\
\
    },\
\
    {\
\
      from: "spider-man.jpg",\
\
      to: "spider-man.jpg",\
\
    },\
\
    { from: "thor.jpg", to: "thor.jpg" },\
\
    {\
\
      from: "captain-america.jpg",\
\
      to: "captain-america.jpg",\
\
    },\
\
],

url: true,

});

const app = new sst.aws.Function("MyApp", {

handler: "index.app",

link: [OpenAiApiKey, vector],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-vector).

* * *

## [React SPA with Vite](https://sst.dev/docs/examples/\#react-spa-with-vite)

Deploy a React single-page app (SPA) with Vite to S3 and CloudFront.

```
new sst.aws.StaticSite("Web", {

build: {

command: "pnpm run build",

output: "dist",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-vite).

* * *

## [AWS Workflow Bus](https://sst.dev/docs/examples/\#aws-workflow-bus)

Creates an [AWS Lambda durable workflow](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)
and triggers it with a [`Bus`](https://sst.dev/docs/component/aws/bus).

```
const workflow = new sst.aws.Workflow("MyWorkflow", {

handler: "src/workflow.handler",

});

const bus = new sst.aws.Bus("Bus");

bus.subscribe("Workflow", workflow, {

pattern: {

detailType: ["app.workflow.requested"],

},

});

const publisher = new sst.aws.Function("Publisher", {

handler: "src/publisher.handler",

url: true,

link: [bus],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-workflow-bus).

* * *

## [AWS Workflow Cron](https://sst.dev/docs/examples/\#aws-workflow-cron)

Creates an [AWS Lambda durable workflow](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)
and triggers it on a schedule using [`CronV2`](https://sst.dev/docs/component/aws/cron-v2).

Since `CronV2` accepts a `Workflow`, the setup is just:

```
const workflow = new sst.aws.Workflow("MyWorkflow", {

handler: "src/workflow.handler",

});

new sst.aws.CronV2("MyCron", {

schedule: "rate(1 minute)",

function: workflow,

});
```

```
const workflow = new sst.aws.Workflow("MyWorkflow", {

handler: "src/workflow.handler",

});

const cron = new sst.aws.CronV2("MyCron", {

schedule: "rate(1 minute)",

function: workflow,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-workflow-cron).

* * *

## [AWS Workflow Python](https://sst.dev/docs/examples/\#aws-workflow-python)

Uses the [`Workflow`](https://sst.dev/docs/component/aws/workflow) component to create an
[AWS Lambda durable workflow](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)
using the Python runtime.

Hit the `Invoker` URL to start the workflow. The workflow logs a callback URL
with a `token` query parameter. Open that URL to resume the waiting step.

```
const workflow = new sst.aws.Workflow("Workflow", {

handler: "workflow/main.handler",

runtime: "python3.13",

});

const resolver = new sst.aws.Function("Resolver", {

handler: "resolver/main.handler",

runtime: "python3.13",

url: true,

link: [workflow],

});

const invoker = new sst.aws.Function("Invoker", {

handler: "invoker/main.handler",

runtime: "python3.13",

url: true,

link: [workflow, resolver],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-workflow-python).

* * *

## [AWS Workflow](https://sst.dev/docs/examples/\#aws-workflow)

Uses the [`Workflow`](https://sst.dev/docs/component/aws/workflow) component to create an
[AWS Lambda durable workflow](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html).

Hit the `Invoker` URL to start the workflow. The workflow logs a callback URL
with a `token` query parameter. Open that URL to resume the waiting step.

```
const workflow = new sst.aws.Workflow("Workflow", {

handler: "src/workflow.handler",

});

const resolver = new sst.aws.Function("Resolver", {

handler: "src/resolver.handler",

url: true,

link: [workflow],

});

const invoker = new sst.aws.Function("Invoker", {

handler: "src/invoker.handler",

url: true,

link: [workflow, resolver],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-workflow).

* * *

## [Zero sync engine](https://sst.dev/docs/examples/\#zero-sync-engine)

Deploy the Zero sync engine with a Postgres database configured for logical
replication in a VPC cluster.

```
const vpc = new sst.aws.Vpc("Vpc", {

bastion: true,

});

const db = new sst.aws.Postgres("Database", {

vpc,

transform: {

parameterGroup: {

parameters: [\
\
        {\
\
          name: "rds.logical_replication",\
\
          value: "1",\
\
          applyMethod: "pending-reboot",\
\
        },\
\
        {\
\
          name: "rds.force_ssl",\
\
          value: "0",\
\
          applyMethod: "pending-reboot",\
\
        },\
\
        {\
\
          name: "max_connections",\
\
          value: "1000",\
\
          applyMethod: "pending-reboot",\
\
        },\
\
      ],

},

},

});

const cluster = new sst.aws.Cluster("Cluster", { vpc });

const connection = $interpolate`postgres://${db.username}:${db.password}@${db.host}:${db.port}`;

new sst.aws.Service("Zero", {

cluster,

image: "rocicorp/zero",

dev: {

command: "npx zero-cache",

},

loadBalancer: {

ports: [{ listen: "80/http", forward: "4848/http" }],

},

environment: {

ZERO_UPSTREAM_DB: $interpolate`${connection}/${db.database}`,

ZERO_CVR_DB: $interpolate`${connection}/${db.database}_cvr`,

ZERO_CHANGE_DB: $interpolate`${connection}/${db.database}_change`,

ZERO_REPLICA_FILE: "zero.db",

ZERO_NUM_SYNC_WORKERS: "1",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/aws-zero).

* * *

## [Cloudflare Astro](https://sst.dev/docs/examples/\#cloudflare-astro)

Deploy an [Astro](https://astro.build/) site to Cloudflare.

```
const bucket = new sst.cloudflare.Bucket("MyBucket");

const kv = new sst.cloudflare.Kv("MyKv");

new sst.cloudflare.Astro("MyWeb", {

link: [bucket, kv],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/cloudflare-astro).

* * *

## [Cloudflare Cron](https://sst.dev/docs/examples/\#cloudflare-cron)

This example creates a Cloudflare Worker that runs on a schedule.

```
const cron = new sst.cloudflare.Cron("Cron", {

job: "index.ts",

schedules: ["* * * * *"]

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/cloudflare-cron).

* * *

## [Cloudflare Durable Object](https://sst.dev/docs/examples/\#cloudflare-durable-object)

This example creates a Durable Object and links it to a worker.

Send a `GET` request to the `url` output. The worker calls the Durable
Object, and the Durable Object logs the current count.

```
const counter = new sst.cloudflare.DurableObject("Counter", {

className: "Counter",

});

const api = new sst.cloudflare.Worker("Api", {

migrations: [\
\
    {\
\
      tag: "v1",\
\
      newSqliteClasses: [counter.className],\
\
    },\
\
],

handler: "worker.ts",

link: [counter],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/cloudflare-durable-object).

* * *

## [Cloudflare Hyperdrive with AWS Postgres](https://sst.dev/docs/examples/\#cloudflare-hyperdrive-with-aws-postgres)

Connect a Cloudflare Worker to an AWS RDS Postgres database through Cloudflare
Hyperdrive. Since RDS lives in a private VPC, a Cloudflare tunnel runs on
Fargate to expose the database to Hyperdrive. Cloudflare Access locks the
tunnel down to Hyperdrive using a service token.

The worker uses the `sst.cloudflare.Hyperdrive` binding. The lambda connects
directly through the VPC for comparison.

```
const domain = "sst-dev.org";

const vpc = new sst.aws.Vpc("Vpc", { nat: "managed" });

const cluster = new sst.aws.Cluster("Cluster", { vpc });

const postgres = new sst.aws.Postgres("Postgres", { vpc });

const zone = cloudflare.getZoneOutput({ filter: { name: domain } });

const tunnelSecret = new random.RandomString("TunnelSecret", {

length: 32,

});

const tunnel = new cloudflare.ZeroTrustTunnelCloudflared("Tunnel", {

accountId: sst.cloudflare.DEFAULT_ACCOUNT_ID,

name: `${$app.name}-${$app.stage}-tunnel`,

tunnelSecret: tunnelSecret.result.apply((v) =>

Buffer.from(v).toString("base64"),

),

});

const record = new cloudflare.DnsRecord("TunnelRecord", {

name: `hyperdrive-${$app.stage}.${domain}`,

ttl: 1,

type: "CNAME",

zoneId: zone.zoneId,

content: $interpolate`${tunnel.id}.cfargotunnel.com`,

proxied: true,

});

const tunnelConfig = new cloudflare.ZeroTrustTunnelCloudflaredConfig(

"TunnelConfig",

{

accountId: sst.cloudflare.DEFAULT_ACCOUNT_ID,

tunnelId: tunnel.id,

config: {

ingresses: [\
\
        {\
\
          hostname: record.name,\
\
          service: $interpolate`tcp://${postgres.host}:${postgres.port}`,\
\
        },\
\
        { service: "http_status:404" },\
\
      ],

},

},

);

const tunnelToken = cloudflare.getZeroTrustTunnelCloudflaredTokenOutput({

accountId: sst.cloudflare.DEFAULT_ACCOUNT_ID,

tunnelId: tunnel.id,

}).token;

const serviceToken = new cloudflare.ZeroTrustAccessServiceToken(

"HyperdriveServiceToken",

{

name: `${$app.name}-${$app.stage}-hyperdrive-token`,

accountId: sst.cloudflare.DEFAULT_ACCOUNT_ID,

},

);

new cloudflare.ZeroTrustAccessApplication("HyperdriveAccess", {

accountId: sst.cloudflare.DEFAULT_ACCOUNT_ID,

type: "self_hosted",

name: `${$app.name}-${$app.stage}-hyperdrive`,

domain: record.name,

destinations: [{ uri: record.name, type: "public" }],

appLauncherVisible: false,

policies: [\
\
    {\
\
      decision: "non_identity",\
\
      includes: [\
\
        { serviceToken: { tokenId: serviceToken.id } },\
\
      ],\
\
      name: `${$app.name}-${$app.stage}-hyperdrive-policy`,\
\
    },\
\
],

});

const cloudflaredService = new sst.aws.Service(

"Cloudflared",

{

wait: true,

capacity: "spot",

cluster,

containers: [\
\
      {\
\
        name: "cloudflared",\
\
        image: "cloudflare/cloudflared:latest",\
\
        command: ["tunnel", "run"],\
\
        environment: {\
\
          TUNNEL_TOKEN: tunnelToken,\
\
          TUNNEL_METRICS: "0.0.0.0:20241",\
\
        },\
\
        health: {\
\
          command: [\
\
            "CMD",\
\
            "cloudflared",\
\
            "tunnel",\
\
            "--metrics",\
\
            "localhost:20241",\
\
            "ready",\
\
          ],\
\
          startPeriod: "60 seconds",\
\
          timeout: "5 seconds",\
\
          interval: "30 seconds",\
\
          retries: 3,\
\
        },\
\
        dev: {\
\
          autostart: true,\
\
          command: $interpolate`docker run \\
\
            --rm \\
\
            -e TUNNEL_LOGLEVEL=info \\
\
            --network ${$app.name} \\
\
            --name ${$app.name}-${$app.stage}-cloudflared \\
\
            cloudflare/cloudflared:latest \\
\
            tunnel run --token ${tunnelToken}`,\
\
        },\
\
      },\
\
    ],

},

// Make sure the tunnel's ingress rules exist before the container starts,

// otherwise cloudflared serves 503s until it re-polls the config.

{ dependsOn: [tunnelConfig] },

);

const hyperdrive = new sst.cloudflare.Hyperdrive(

"Database",

{

origin: {

host: record.name,

user: postgres.username,

password: postgres.password,

database: postgres.database,

accessClientId: serviceToken.clientId,

accessClientSecret: serviceToken.clientSecret,

scheme: "postgres",

},

},

{

dependsOn: [\
\
      postgres,\
\
      tunnel,\
\
      record,\
\
      tunnelConfig,\
\
      cloudflaredService,\
\
      cluster,\
\
    ],

},

);

const worker = new sst.cloudflare.Worker("Worker", {

handler: "./worker.ts",

link: [hyperdrive],

url: true,

});

const lambda = new sst.aws.Function("Lambda", {

handler: "lambda.handler",

link: [postgres],

vpc,

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/cloudflare-hyperdrive-aws).

* * *

## [Cloudflare Hyperdrive PlanetScale](https://sst.dev/docs/examples/\#cloudflare-hyperdrive-planetscale)

Connect a Cloudflare Worker to a PlanetScale Postgres database through
Cloudflare Hyperdrive.

```
const db = planetscale.getDatabaseVitessOutput({

id: "mydb",

organization: "myorg",

});

const branch =

$app.stage === "production"

? planetscale.getPostgresBranchOutput({

id: db.defaultBranch,

database: db.name,

organization: db.organization,

})

: new planetscale.PostgresBranch("DatabaseBranch", {

database: db.name,

name: $app.stage,

organization: db.organization,

parentBranch: db.defaultBranch,

});

const role = new planetscale.PostgresBranchRole("DatabaseRole", {

branch: branch.name,

database: db.name,

inheritedRoles: ["pg_read_all_data", "pg_write_all_data"],

name: `${$app.name}-${$app.stage}`,

organization: db.organization,

});

const hyperdrive = new sst.cloudflare.Hyperdrive("Database", {

origin: {

host: role.accessHostUrl,

database: role.databaseName,

user: role.username,

password: role.password,

port: 6432, // Use 5432 for direct connection instead of PgBouncer

scheme: "postgres",

},

caching: false,

});

const worker = new sst.cloudflare.Worker("Worker", {

handler: "./worker.ts",

link: [hyperdrive],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/cloudflare-hyperdrive-planetscale).

* * *

## [Cloudflare KV](https://sst.dev/docs/examples/\#cloudflare-kv)

This example creates a Cloudflare KV namespace and links it to a worker. Now you can use the
SDK to interact with the KV namespace in your worker.

```
const storage = new sst.cloudflare.Kv("MyStorage");

const worker = new sst.cloudflare.Worker("Worker", {

url: true,

link: [storage],

handler: "index.ts",

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/cloudflare-kv).

* * *

## [Cloudflare Queue](https://sst.dev/docs/examples/\#cloudflare-queue)

This example creates a Cloudflare Queue with a producer and consumer worker.

```
const queue = new sst.cloudflare.Queue("MyQueue");

queue.subscribe("consumer.ts");

const producer = new sst.cloudflare.Worker("Producer", {

handler: "producer.ts",

link: [queue],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/cloudflare-queue).

* * *

## [Cloudflare Rate Limit](https://sst.dev/docs/examples/\#cloudflare-rate-limit)

This example creates a Cloudflare Rate Limit and a Worker that applies it.

```
const rateLimit = new sst.cloudflare.RateLimit("MyRateLimit", {

namespaceId: 1001,

limit: 100,

period: "1 minute",

});

const worker = new sst.cloudflare.Worker("MyWorker", {

handler: "./index.ts",

url: true,

link: [rateLimit],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/cloudflare-rate-limit).

* * *

## [Cloudflare React Router](https://sst.dev/docs/examples/\#cloudflare-react-router)

Deploy a [React Router](https://reactrouter.com/) app to Cloudflare.

```
const kv = new sst.cloudflare.Kv("MyKv");

new sst.cloudflare.ReactRouter("MyWeb", {

link: [kv],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/cloudflare-react-router).

* * *

## [Cloudflare TanStack Start](https://sst.dev/docs/examples/\#cloudflare-tanstack-start)

Deploy a [TanStack Start](https://tanstack.com/start/latest) app to Cloudflare.

```
const kv = new sst.cloudflare.Kv("MyKv");

new sst.cloudflare.TanStackStart("MyWeb", {

link: [kv],

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/cloudflare-tanstack-start).

* * *

## [Cloudflare SPA with Vite](https://sst.dev/docs/examples/\#cloudflare-spa-with-vite)

Deploy a single-page app (SPA) with Vite to Cloudflare.

```
new sst.cloudflare.StaticSiteV2("Vite", {

notFound: "single-page-application",

build: {

command: "pnpm run build",

output: "dist",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/cloudflare-vite).

* * *

## [Cloudflare Workflow](https://sst.dev/docs/examples/\#cloudflare-workflow)

This example creates a Cloudflare Workflow and a Worker that triggers it.

```
const processor = new sst.cloudflare.Workflow("OrderProcessor", {

handler: "./workflow.ts",

className: "OrderProcessor",

});

const api = new sst.cloudflare.Worker("Api", {

handler: "./api.ts",

link: [processor],

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/cloudflare-workflow).

* * *

## [AWS Python uv Workspaces](https://sst.dev/docs/examples/\#aws-python-uv-workspaces)

Deploy Python Lambda functions from a
[uv workspace](https://docs.astral.sh/uv/concepts/projects/workspaces/) that
uses `package = true` and a `src/` layout.

SST traverses up from the handler path to find the nearest `pyproject.toml`.

```
├── sst.config.ts

├── pyproject.toml

├── handler.py

├── src

│   └── myapp

│       ├── __init__.py

│       └── utils.py

└── packages

├── api

│   ├── pyproject.toml

│   └── src/api

│       ├── __init__.py

│       └── handler.py

├── shared

│   ├── pyproject.toml

│   └── src/shared

│       ├── __init__.py

│       └── models.py

└── worker

├── pyproject.toml

└── src/worker

├── __init__.py

└── handler.py
```

With `package = true`, the package is importable by name instead of `src.*`.

```
[tool.hatch.build.targets.wheel]

packages = ["src/myapp"]
```

Then import it normally in your handler.

```
from myapp import utils
```

Each workspace member can become its own function.

```
new sst.aws.Function("PackageHandler", {

handler: "packages/api/src/api/handler.lambda_handler",

runtime: "python3.11",

url: true,

});
```

```
const rootHandler = new sst.aws.Function("RootHandler", {

handler: "handler.lambda_handler",

runtime: "python3.11",

url: true,

});

const packageHandler = new sst.aws.Function("PackageHandler", {

handler: "packages/api/src/api/handler.lambda_handler",

runtime: "python3.11",

url: true,

});

const workspaceHandler = new sst.aws.Function("WorkspaceHandler", {

handler: "packages/worker/src/worker/handler.lambda_handler",

runtime: "python3.11",

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/python-modern-uv).

* * *

## [Link multiple secrets](https://sst.dev/docs/examples/\#link-multiple-secrets)

You might have multiple secrets that need to be used across your app. It can be tedious to
create a new secret and link it to each function or resource.

A common pattern to addresses this is to create an object with all your secrets and then
link them all at once. Now when you have a new secret, you can add it to the object and
it will be automatically available to all your resources.

```
// Manage all secrets together

const secrets = {

secret1: new sst.Secret("Secret1", "some-secret-value-1"),

secret2: new sst.Secret("Secret2", "some-secret-value-2"),

};

const allSecrets = Object.values(secrets);

const bucket = new sst.aws.Bucket("MyBucket");

const api = new sst.aws.Function("MyApi", {

link: [bucket, ...allSecrets],

handler: "index.handler",

url: true,

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/secret-link-all).

* * *

## [Default function props](https://sst.dev/docs/examples/\#default-function-props)

Set default props for all the functions in your app using the global [`$transform`](https://sst.dev/docs/reference/global/#transform).

```
$transform(sst.aws.Function, (args) => {

args.runtime = "nodejs14.x";

args.environment = {

FOO: "BAR",

};

});

new sst.aws.Function("MyFunction", {

handler: "index.ts",

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/sst-transform).

* * *

## [Vercel domains](https://sst.dev/docs/examples/\#vercel-domains)

Creates a router that uses domains purchased through and hosted in your Vercel account.
Ensure the `VERCEL_API_TOKEN` and `VERCEL_TEAM_ID` environment variables are set.

```
const router = new sst.aws.Router("MyRouter", {

domain: {

name: "ion.sst.moe",

dns: sst.vercel.dns({ domain: "sst.moe" }),

},

routes: {

"/*": "https://sst.dev",

},

});
```

View the [full example](https://github.com/anomalyco/sst/tree/dev/examples/vercel-domain).

reCAPTCHA

Recaptcha requires verification.

protected by **reCAPTCHA**
