Skip to content

Examples

참고용 예제 앱 모음.

예제

아래는 SST 앱 예제 모음입니다. 이 예제들은 저장소의 examples/ 디렉토리에서 확인할 수 있습니다.

이 예제들의 설명은 앱의 sst.config.ts 파일에 있는 주석을 사용하여 생성되었습니다.

기여하기

예제를 추가하거나 수정하려면 저장소에 PR을 제출하세요.
예제에서 sst.config.ts를 문서화했는지 확인하세요.

API Gateway 인증

API Gateway 라우트에 IAM 및 JWT 인증자를 활성화합니다.

sst.config.ts
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");
return {
api: api.url,
};

전체 예제 보기.

AWS Astro 컨테이너와 Redis

Astro와 Redis를 사용하여 히트 카운터 앱을 만듭니다.

이 예제는 Astro를 Fargate 서비스로 ECS에 배포하고 Redis와 연결합니다.

sst.config.ts
cluster.addService("MyService", {
link: [redis],
loadBalancer: {
ports: [{ listen: "80/http", forward: "3000/http" }],
},
dev: {
command: "npm run dev",
},
});

Redis 클러스터가 VPC에 있기 때문에 로컬 머신에서 연결하려면 터널이 필요합니다.

Terminal window
sudo npx sst tunnel install

이 명령은 머신에 네트워크 인터페이스를 생성하기 위해 sudo 권한이 필요합니다. 이 작업은 머신에서 한 번만 수행하면 됩니다.

앱을 로컬에서 실행하려면 다음 명령을 실행하세요.

Terminal window
npx sst dev

이제 http://localhost:4321로 이동하면 페이지를 새로고침할 때마다 카운터가 업데이트되는 것을 확인할 수 있습니다.

마지막으로, 이 예제에 포함된 Dockerfile을 추가하고 npx sst deploy --stage production을 실행하여 배포할 수 있습니다.

sst.config.ts
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 });
cluster.addService("MyService", {
link: [redis],
loadBalancer: {
ports: [{ listen: "80/http", forward: "4321/http" }],
},
dev: {
command: "npm run dev",
},
});

전체 예제 보기.

AWS Astro 스트리밍

Astro 스트리밍 가이드를 따라 HTML을 스트리밍하는 앱을 만듭니다.

astro-sst 어댑터에서 responseMode를 설정하여 스트리밍을 활성화합니다.

astro.config.mjs
adapter: aws({
responseMode: "stream"
})

이제 Promise를 반환하는 모든 컴포넌트가 스트리밍됩니다.

src/components/Friends.astro
---
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>

3초 지연 후 friends 섹션이 로드되는 것을 확인할 수 있습니다.

Safari는 데이터를 스트리밍할 시점을 결정하기 위해 다른 휴리스틱을 사용합니다. 스트리밍을 트리거하려면 충분한 초기 HTML을 렌더링해야 합니다. 이는 일반적으로 데모 앱에서만 문제가 됩니다.

Astro 컴포넌트에서 스트리밍을 위해 설정할 것은 없습니다.

sst.config.ts
new sst.aws.Astro("MyWeb");

전체 예제를 확인하세요.

AWS Aurora 로컬

이 예제에서는 개발 환경에서 로컬로 실행 중인 Postgres 인스턴스에 연결합니다. 배포 시에는 RDS Aurora를 사용합니다.

로컬에서 Postgres를 실행하기 위해 docker run CLI를 사용합니다. Docker를 사용하지 않아도 되며, Postgres.app이나 다른 방법으로 로컬에서 Postgres를 실행할 수 있습니다.

Terminal window
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

데이터는 .sst/storage 디렉토리에 저장됩니다. 따라서 개발 서버를 다시 시작해도 데이터는 그대로 유지됩니다.

그런 다음 Aurora 컴포넌트의 dev 속성을 로컬 Postgres 인스턴스 설정으로 구성합니다.

sst.config.ts
dev: {
username: "postgres",
password: "password",
database: "local",
port: 5432,
}

Postgres에 dev 속성을 제공하면 SST는 sst dev를 실행할 때 새로운 RDS 데이터베이스를 배포하지 않고 로컬 Postgres 인스턴스를 사용합니다.

또한 로컬에서 실행 중인지 조건부로 확인하지 않고도 Resource link를 통해 데이터베이스에 접근할 수 있습니다.

index.ts
const pool = new Pool({
host: Resource.MyPostgres.host,
port: Resource.MyPostgres.port,
user: Resource.MyPostgres.username,
password: Resource.MyPostgres.password,
database: Resource.MyPostgres.database,
});

위 코드는 sst devsst deploy 모두에서 작동합니다.

sst.config.ts
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",
});

전체 예제를 확인하세요.

AWS Aurora MySQL

이 예제에서는 Aurora MySQL 데이터베이스를 배포합니다.

sst.config.ts
const mysql = new sst.aws.Aurora("MyDatabase", {
engine: "mysql",
vpc,
});

그리고 이를 Lambda 함수에 연결합니다.

sst.config.ts
new sst.aws.Function("MyApp", {
handler: "index.handler",
link: [mysql],
url: true,
vpc,
});

이제 함수에서 데이터베이스에 접근할 수 있습니다.

index.ts
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,
});

또한 VPC에 bastion 옵션을 활성화합니다. 이렇게 하면 sst tunnel CLI를 사용해 로컬 머신에서 데이터베이스에 연결할 수 있습니다.

Terminal window
sudo npx sst tunnel install

이 명령은 머신에 네트워크 인터페이스를 생성하기 위해 sudo 권한이 필요합니다. 이 작업은 머신에서 한 번만 수행하면 됩니다.

이제 npx sst dev를 실행하면 로컬 머신에서 데이터베이스에 연결할 수 있습니다.

sst.config.ts
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,
});
return {
host: mysql.host,
port: mysql.port,
username: mysql.username,
password: mysql.password,
database: mysql.database,
};

전체 예제 보기.

AWS Aurora Postgres

이 예제에서는 Aurora Postgres 데이터베이스를 배포합니다.

sst.config.ts
const postgres = new sst.aws.Aurora("MyDatabase", {
engine: "postgres",
vpc,
});

그리고 이를 Lambda 함수에 연결합니다.

sst.config.ts
new sst.aws.Function("MyApp", {
handler: "index.handler",
link: [postgres],
url: true,
vpc,
});

함수 내에서는 postgres 패키지를 사용합니다.

index.ts
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,
});

또한 VPC에 bastion 옵션을 활성화합니다. 이를 통해 sst tunnel CLI를 사용해 로컬 머신에서 데이터베이스에 연결할 수 있습니다.

Terminal window
sudo npx sst tunnel install

이 명령은 머신에 네트워크 인터페이스를 생성하기 위해 sudo 권한이 필요합니다. 이 작업은 머신에서 한 번만 수행하면 됩니다.

이제 npx sst dev를 실행하면 로컬 머신에서 데이터베이스에 연결할 수 있습니다.

sst.config.ts
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,
});
return {
host: postgres.host,
port: postgres.port,
username: postgres.username,
password: postgres.password,
database: postgres.database,
};

전체 예제를 확인하세요.

버킷 정책

S3 버킷을 생성하고 버킷 정책을 변환합니다.

sst.config.ts
const bucket = new sst.aws.Bucket("MyBucket", {
transform: {
policy: (args) => {
// sst.aws.iamEdit 헬퍼 함수를 사용하여 IAM 정책을 조작
// 컴포넌트의 Output 값을 포함
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}/*`,
});
});
},
},
});
return {
bucket: bucket.name,
};

전체 예제 보기.

버킷 큐 알림

S3 버킷을 생성하고 SQS 큐를 통해 이벤트를 구독합니다.

sst.config.ts
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:*"],
},
],
});
return {
bucket: bucket.name,
queue: queue.url,
};

전체 예제를 확인하세요.

버킷 알림

S3 버킷을 생성하고 함수로 이벤트를 구독합니다.

sst.config.ts
const bucket = new sst.aws.Bucket("MyBucket");
bucket.notify({
notifications: [
{
name: "MySubscriber",
function: "subscriber.handler",
events: ["s3:ObjectCreated:*"],
},
],
});
return {
bucket: bucket.name,
};

전체 예제를 확인하세요.

버킷 토픽 알림

S3 버킷을 생성하고 SNS 토픽을 통해 이벤트를 구독합니다.

sst.config.ts
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:*"],
},
],
});
return {
bucket: bucket.name,
topic: topic.name,
};

전체 예제 보기.

AWS Bun Elysia 컨테이너

Bun Elysia API를 AWS에 배포합니다.

다음 명령어로 시작할 수 있습니다.

Terminal window
bun create elysia aws-bun-elysia
cd aws-bun-elysia
bunx sst init

이제 서비스를 추가할 수 있습니다.

sst.config.ts
cluster.addService("MyService", {
loadBalancer: {
ports: [{ listen: "80/http", forward: "3000/http" }],
},
dev: {
command: "bun dev",
},
});

로컬에서 앱을 시작합니다.

Terminal window
bun sst dev

이 예제는 파일을 S3에 업로드한 후 다운로드할 수 있게 합니다.

Terminal window
curl -F file=@elysia.png http://localhost:3000/
curl http://localhost:3000/latest

마지막으로 bun sst deploy --stage production을 사용해 배포할 수 있습니다.

sst.config.ts
const bucket = new sst.aws.Bucket("MyBucket");
const vpc = new sst.aws.Vpc("MyVpc");
const cluster = new sst.aws.Cluster("MyCluster", { vpc });
cluster.addService("MyService", {
loadBalancer: {
ports: [{ listen: "80/http", forward: "3000/http" }],
},
dev: {
command: "bun dev",
},
link: [bucket],
});

전체 예제를 확인하세요.

AWS Bun Redis

Bun과 Redis를 사용하여 히트 카운터 앱을 만듭니다.

이 예제는 Bun을 Fargate 서비스로 ECS에 배포하고 Redis와 연결합니다.

sst.config.ts
cluster.addService("MyService", {
loadBalancer: {
ports: [{ listen: "80/http", forward: "3000/http" }],
},
dev: {
command: "bun dev",
},
link: [redis],
});

또한 몇 가지 스크립트가 있습니다. 개발용 dev 스크립트와 프로덕션 배포 시 사용되는 build 스크립트가 있습니다.

package.json
{
"scripts": {
"dev": "bun run --watch index.ts",
"build": "bun build --target bun index.ts"
},
}

Redis 클러스터가 VPC에 있기 때문에 로컬 머신에서 연결하려면 터널이 필요합니다.

Terminal window
sudo bun sst tunnel install

이 명령은 머신에 네트워크 인터페이스를 생성하기 위해 sudo 권한이 필요합니다. 이 작업은 머신에서 한 번만 수행하면 됩니다.

앱을 로컬에서 실행하려면 다음 명령을 사용합니다.

Terminal window
bun sst dev

이제 http://localhost:3000으로 이동하면 페이지를 새로고침할 때마다 카운터가 업데이트되는 것을 볼 수 있습니다.

마지막으로, 예제에 포함된 Dockerfile을 사용하여 bun sst deploy --stage production 명령으로 배포할 수 있습니다.

sst.config.ts
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 });
cluster.addService("MyService", {
link: [redis],
loadBalancer: {
ports: [{ listen: "80/http", forward: "3000/http" }],
},
dev: {
command: "bun dev",
},
});

전체 예제를 확인하세요.

AWS 클러스터 프라이빗 서비스

loadBalancer.public 속성을 false로 설정하여 서비스에 프라이빗 로드 밸런서를 추가합니다.

이를 통해 VPC 내부에서만 접근할 수 있는 내부 서비스를 생성할 수 있습니다.

sst.config.ts
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });
const cluster = new sst.aws.Cluster("MyCluster", { vpc });
cluster.addService("MyService", {
loadBalancer: {
public: false,
ports: [{ listen: "80/http" }],
},
});

전체 예제 보기.

API Gateway를 사용한 AWS 클러스터

VPC 링크를 통해 API Gateway HTTP API로 서비스를 노출합니다.

이 방법은 로드 밸런서를 사용하는 대안입니다. API Gateway는 요청당 비용을 청구하기 때문에 트래픽이 많지 않은 서비스에 더 경제적입니다.

서비스에서 어떤 포트를 API Gateway를 통해 노출할지 지정해야 합니다.

sst.config.ts
const service = cluster.addService("MyService", {
serviceRegistry: {
port: 80,
},
});

API Gateway HTTP API도 서비스와 동일한 VPC에 있어야 합니다.

sst.config.ts
const vpc = new sst.aws.Vpc("MyVpc");
const cluster = new sst.aws.Cluster("MyCluster", { vpc });
const service = cluster.addService("MyService", {
serviceRegistry: {
port: 80,
},
});
const api = new sst.aws.ApiGatewayV2("MyApi", { vpc });
api.routePrivate("$default", service.nodes.cloudmapService.arn);

전체 예제를 확인하세요.

큐 구독하기

SQS 큐를 생성하고, 이를 구독하며, 함수에서 메시지를 발행합니다.

sst.config.ts
// 데드 레터 큐 생성
const dlq = new sst.aws.Queue("DeadLetterQueue");
dlq.subscribe("subscriber.dlq");
// 메인 큐 생성
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,
});
return {
app: app.url,
queue: queue.url,
dlq: dlq.url,
};

전체 예제 보기.

AWS Deno Redis

Deno와 Redis를 사용하여 히트 카운터 앱을 만듭니다.

이 예제는 Deno를 Fargate 서비스로 ECS에 배포하고 Redis와 연결합니다.

sst.config.ts
cluster.addService("MyService", {
link: [redis],
loadBalancer: {
ports: [{ listen: "80/http", forward: "8000/http" }],
},
dev: {
command: "deno task dev",
},
});

Redis 클러스터가 VPC에 있기 때문에 로컬 머신에서 연결하려면 터널이 필요합니다.

Terminal window
sudo sst tunnel install

이 명령은 머신에 네트워크 인터페이스를 생성하기 위해 sudo 권한이 필요합니다. 이 작업은 머신에서 한 번만 수행하면 됩니다.

앱을 로컬에서 실행하려면 다음 명령을 실행하세요.

Terminal window
sst dev

이제 http://localhost:8000으로 접속하면 페이지를 새로고침할 때마다 카운터가 업데이트되는 것을 확인할 수 있습니다.

마지막으로, 예제에 포함된 Dockerfile을 사용하여 sst deploy --stage production 명령으로 배포할 수 있습니다.

sst.config.ts
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 });
cluster.addService("MyService", {
link: [redis],
loadBalancer: {
ports: [{ listen: "80/http", forward: "8000/http" }],
},
dev: {
command: "deno task dev",
},
});

전체 예제를 확인하세요.

DynamoDB 스트림

DynamoDB 테이블을 생성하고 스트림을 활성화한 후, 함수를 통해 구독합니다.

sst.config.ts
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 app = new sst.aws.Function("MyApp", {
handler: "publisher.handler",
link: [table],
url: true,
});
return {
app: app.url,
table: table.name,
};

전체 예제 보기.

Pulumi로 EC2 생성하기

Pulumi 리소스를 직접 사용해 EC2 인스턴스를 생성합니다.

sst.config.ts
// pulumi를 import할 필요 없이, 이미 sst에 포함되어 있습니다.
const securityGroup = new aws.ec2.SecurityGroup("web-secgrp", {
ingress: [
{
protocol: "tcp",
fromPort: 80,
toPort: 80,
cidrBlocks: ["0.0.0.0/0"],
},
],
});
// 최신 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
});
// 간단한 웹 서버 설정을 위한 사용자 데이터
const userData = `#!/bin/bash
ho "Hello, World!" > index.html
hup python3 -m http.server 80 &`;
// EC2 인스턴스 생성
const server = new aws.ec2.Instance("web-server", {
instanceType: "t2.micro",
ami: ami.then((ami) => ami.id),
userData: userData,
vpcSecurityGroupIds: [securityGroup.id],
associatePublicIpAddress: true,
});
return {
app: server.publicIp,
};

전체 예제 보기.

AWS EFS와 SQLite 사용하기

EFS 파일 시스템을 함수에 마운트하고 SQLite 데이터베이스에 데이터를 쓸 수 있습니다.

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

파일 시스템은 함수 내에서 /mnt/efs 경로에 마운트됩니다.

이 예제는 데모 목적으로만 제공됩니다. 프로덕션 환경에서 EFS를 데이터베이스 용도로 사용하는 것은 권장하지 않습니다.

sst.config.ts
// Lambda 함수를 위해 NAT Gateway가 필요합니다
const vpc = new sst.aws.Vpc("MyVpc", { nat: "managed" });
// SQLite 데이터베이스를 저장할 EFS 파일 시스템 생성
const efs = new sst.aws.Efs("MyEfs", { vpc });
// 데이터베이스를 쿼리하는 Lambda 함수 생성
new sst.aws.Function("MyFunction", {
vpc,
url: true,
volume: {
efs,
path: "/mnt/efs",
},
handler: "index.handler",
nodejs: {
install: ["better-sqlite3"],
},
});

전체 예제 보기.

AWS EFS와 SurrealDB

SurrealDB 도커 이미지를 사용해 컨테이너에서 서버를 실행하고, EFS를 파일 시스템으로 사용합니다.

sst.config.ts
const server = cluster.addService("MyService", {
architecture: "arm64",
image: "surrealdb/surrealdb:v2.0.2",
// ...
volumes: [
{ efs, path: "/data" },
],
});

그런 다음 Lambda 함수에서 서버에 연결합니다.

index.ts
const endpoint = `http://${Resource.MyConfig.host}:${Resource.MyConfig.port}`;
const db = new Surreal();
await db.connect(endpoint);

이 코드는 SurrealDB 클라이언트를 사용해 서버에 연결합니다.

이 예제는 데모 목적으로만 제공됩니다. 프로덕션 환경에서 데이터베이스 용도로 EFS를 사용하는 것은 권장하지 않습니다.

sst.config.ts
const { RandomPassword } = await import("@pulumi/random");
// SurrealDB 자격 증명
const PORT = 8080;
const NAMESPACE = "test";
const DATABASE = "test";
const USERNAME = "root";
const PASSWORD = new RandomPassword("Password", {
length: 32,
}).result;
// Lambda 함수를 위해 NAT 게이트웨이가 필요합니다
const vpc = new sst.aws.Vpc("MyVpc", { nat: "managed" });
// SurrealDB 데이터를 EFS에 저장
const efs = new sst.aws.Efs("MyEfs", { vpc });
// 컨테이너에서 SurrealDB 서버 실행
const cluster = new sst.aws.Cluster("MyCluster", { vpc });
const server = cluster.addService("MyService", {
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" },
],
});
// SurrealDB에 연결할 Lambda 클라이언트
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,
});

전체 예제 보기.

AWS EFS

함수와 컨테이너에 EFS 파일 시스템을 마운트합니다.

이렇게 하면 함수와 컨테이너 모두 동일한 파일 시스템에 접근할 수 있습니다. 여기서 둘 다 파일 시스템에 저장된 카운터를 업데이트합니다.

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

파일 시스템은 함수와 컨테이너 모두에서 /mnt/efs에 마운트됩니다.

sst.config.ts
// Lambda 함수를 위해 NAT Gateway가 필요합니다
const vpc = new sst.aws.Vpc("MyVpc", { nat: "managed" });
// 카운터를 저장할 EFS 파일 시스템 생성
const efs = new sst.aws.Efs("MyEfs", { vpc });
// 카운터를 증가시키는 Lambda 함수 생성
new sst.aws.Function("MyFunction", {
handler: "lambda.handler",
url: true,
vpc,
volume: {
efs,
path: "/mnt/efs",
},
});
// 동일한 카운터를 증가시키는 서비스 생성
const cluster = new sst.aws.Cluster("MyCluster", { vpc });
cluster.addService("MyService", {
loadBalancer: {
ports: [{ listen: "80/http" }],
},
volumes: [
{
efs,
path: "/mnt/efs",
},
],
});

전체 예제를 확인하세요.

AWS Express Redis

Express와 Redis를 사용하여 히트 카운터 앱을 만듭니다.

이 예제는 Express를 Fargate 서비스로 ECS에 배포하고 Redis와 연결합니다.

sst.config.ts
cluster.addService("MyService", {
loadBalancer: {
ports: [{ listen: "80/http" }],
},
dev: {
command: "node --watch index.mjs",
},
link: [redis],
});

Redis 클러스터가 VPC 내에 있기 때문에 로컬 머신에서 연결하려면 터널이 필요합니다.

Terminal window
sudo npx sst tunnel install

이 명령은 머신에 네트워크 인터페이스를 생성하기 위해 sudo 권한이 필요합니다. 이 작업은 머신에서 한 번만 수행하면 됩니다.

앱을 로컬에서 실행하려면 다음 명령을 실행하세요.

Terminal window
npx sst dev

이제 http://localhost:80으로 접속하면 페이지를 새로고침할 때마다 카운터가 업데이트되는 것을 확인할 수 있습니다.

마지막으로, 예제에 포함된 Dockerfile을 사용하여 npx sst deploy --stage production 명령으로 배포할 수 있습니다.

sst.config.ts
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 });
cluster.addService("MyService", {
link: [redis],
loadBalancer: {
ports: [{ listen: "80/http" }],
},
dev: {
command: "node --watch index.mjs",
},
});

전체 예제를 확인하세요.

Lambda에서 FFmpeg 사용하기

FFmpeg를 사용하여 비디오를 처리합니다. 이 예제에서는 clip.mp4 파일에서 단일 프레임을 추출합니다.

모든 아키텍처에 대해 미리 빌드된 바이너리를 포함하는 ffmpeg-static 패키지를 사용합니다.

index.ts
import ffmpeg from "ffmpeg-static";

이를 사용하여 자식 프로세스를 생성하고 FFmpeg를 실행할 수 있습니다.

index.ts
spawnSync(ffmpeg, ffmpegParams, { stdio: "pipe" });

이를 배포할 때 레이어가 필요하지 않습니다. SST가 대상 Lambda 아키텍처(예: arm64)에 맞는 바이너리를 사용하기 때문입니다.

sst.config.ts
{
nodejs: { install: ["ffmpeg-static"] }
}

이 모든 것은 nodejs.install에 의해 처리됩니다.

sst.config.ts
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"] },
});
return {
url: func.url,
};

전체 예제를 확인하세요.

AWS ApiGatewayV2 Go

aws-lambda-go-api-proxy를 사용하여 API Gateway V2와 함께 Go API를 실행할 수 있습니다.

따라서 일반적으로 Go 함수를 작성한 후 이 패키지를 사용하여 API Gateway V2 이벤트를 처리합니다.

main.go
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)
}
sst.config.ts
const api = new sst.aws.ApiGatewayV2("GoApi");
api.route("$default", {
handler: "src/",
runtime: "go",
});

전체 예제를 확인하세요.

AWS Lambda Go S3 Presigned

Go Lambda 함수에서 연결된 S3 버킷에 대한 사전 서명된 URL을 생성합니다.

S3 클라이언트와 PresignedClient를 설정합니다.

main.go
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
panic(err)
}
client := s3.NewFromConfig(cfg)
presignedClient := s3.NewPresignClient(client)

사전 서명된 URL을 생성합니다.

main.go
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),
})
sst.config.ts
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],
});

전체 예제를 확인하세요.

AWS Lambda Go DynamoDB

DynamoDB와 함께 Go 런타임 Lambda를 사용하는 방법에 대한 예제입니다.

DynamoDB 클라이언트를 설정합니다.

src/main.go
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)
}
}

DynamoDB에 요청을 보냅니다.

src/main.go
_, err = r.client.PutItem(ctx, &dynamodb.PutItemInput{
TableName: tableName.(string),
Item: item,
})
sst.config.ts
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],
});

전체 예제를 확인하세요.

AWS Hono 컨테이너와 Redis

Hono와 Redis를 사용하여 히트 카운터 앱을 만듭니다.

이 예제는 Hono API를 Fargate 서비스로 ECS에 배포하고 Redis와 연결합니다.

sst.config.ts
cluster.addService("MyService", {
link: [redis],
loadBalancer: {
ports: [{ listen: "80/http", forward: "3000/http" }],
},
dev: {
command: "npm run dev",
},
});

Redis 클러스터가 VPC에 있기 때문에 로컬 머신에서 연결하려면 터널이 필요합니다.

Terminal window
sudo npx sst tunnel install

이 명령은 머신에 네트워크 인터페이스를 생성하기 위해 sudo 권한이 필요합니다. 이 작업은 머신에서 한 번만 수행하면 됩니다.

앱을 로컬에서 실행하려면 다음 명령을 실행하세요.

Terminal window
npx sst dev

이제 http://localhost:3000으로 이동하면 페이지를 새로고침할 때마다 카운터가 업데이트되는 것을 확인할 수 있습니다.

마지막으로, 다음과 같이 배포할 수 있습니다:

  1. 이 예제에 포함된 Dockerfile을 사용합니다.

  2. TypeScript 파일을 컴파일해야 하므로 tsconfig.json에 다음을 추가합니다.

    tsconfig.json
    {
    "compilerOptions": {
    // ...
    "outDir": "./dist"
    },
    "exclude": ["node_modules"]
    }
  3. TypeScript를 설치합니다.

    Terminal window
    npm install typescript --save-dev
  4. 그리고 package.jsonbuild 스크립트를 추가합니다.

    package.json
    "scripts": {
    // ...
    "build": "tsc"
    }

마지막으로, npx sst deploy --stage production을 실행합니다.

sst.config.ts
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 });
cluster.addService("MyService", {
link: [redis],
loadBalancer: {
ports: [{ listen: "80/http", forward: "3000/http" }],
},
dev: {
command: "npm run dev",
},
});

전체 예제 보기.

AWS Hono 스트리밍

Hono를 사용해 Lambda 함수의 스트리밍을 활성화하는 예제입니다.

sst.config.ts
{
streaming: true
}

sst dev는 스트리밍을 지원하지 않지만, 배포 시 조건부로 활성화할 수 있습니다.

index.ts
export const handler = process.env.SST_LIVE ? handle(app) : streamHandle(app);

이 코드는 sst dev에서 표준 핸들러를 반환합니다.

터미널에서 테스트하려면 curl 명령어에 --no-buffer 옵션을 사용하세요.

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

API Gateway는 스트리밍을 지원하지 않기 때문에 여기서는 Function URL을 직접 사용합니다.

sst.config.ts
const hono = new sst.aws.Function("Hono", {
url: true,
streaming: true,
timeout: "15 minutes",
handler: "index.handler",
});
return {
api: hono.url,
};

전체 예제를 확인하세요.

IAM 권한 경계

앱에서 생성될 모든 IAM 역할에 대한 최대 권한을 설정하려면 권한 경계를 사용하세요.

이 예제에서 함수는 s3:ListAllMyBucketssqs:ListQueues 권한을 가지고 있습니다. 하지만 s3:ListAllMyBuckets만 허용하는 권한 경계를 생성합니다. 그리고 전역 $transform을 사용하여 앱의 모든 역할에 이를 적용합니다.

결과적으로 함수는 S3 버킷만 나열할 수 있습니다. 배포된 URL을 열면 SQS 목록 호출이 실패하는 것을 확인할 수 있습니다.

AWS IAM 권한 경계에 대해 더 알아보세요.

sst.config.ts
// 권한 경계 생성
const permissionsBoundary = new aws.iam.Policy("MyPermissionsBoundary", {
policy: aws.iam.getPolicyDocumentOutput({
statements: [
{
actions: ["s3:ListAllMyBuckets"],
resources: ["*"],
},
],
}).json,
});
// 모든 역할에 경계 적용
$transform(aws.iam.Role, (args) => {
args.permissionsBoundary = permissionsBoundary;
});
// 경계가 이 함수의 역할에 자동으로 적용됨
const app = new sst.aws.Function("MyApp", {
handler: "index.handler",
permissions: [
{
actions: ["s3:ListAllMyBuckets", "sqs:ListQueues"],
resources: ["*"],
},
],
url: true,
});
return {
app: app.url,
};

전체 예제를 확인하세요.

현재 AWS 계정

현재 AWS 계정 정보를 가져오려면 aws.getXXXXOutput() 프로바이더 함수를 사용할 수 있습니다.
프로바이더 함수에 대해 더 알아보세요.

sst.config.ts
return {
region: aws.getRegionOutput().name,
account: aws.getCallerIdentityOutput({}).accountId,
};

전체 예제를 확인하세요.

AWS JSX Email

JSX EmailEmail 컴포넌트를 사용해 이메일을 디자인하고 전송합니다.

이 예제를 테스트하려면 sst.config.ts 파일을 수정해 여러분의 이메일 주소를 사용하세요.

sst.config.ts
sender: "email@example.com"

그리고 다음 명령어를 실행합니다.

Terminal window
npm install
npx sst dev

AWS에서 이메일 주소 확인 요청 메일을 받게 됩니다. 링크를 클릭해 확인하세요.

다음으로, sst dev CLI 출력에 표시된 URL로 이동합니다. 이제 JSX Email로 렌더링된 이메일을 받을 수 있습니다.

index.ts
import { Template } from "./templates/email";
await render(Template({
email: "spongebob@example.com",
name: "Spongebob Squarepants"
}))

프로덕션 환경으로 전환할 준비가 되면 다음 작업을 수행할 수 있습니다.

sst.config.ts
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,
});
return {
api: api.url,
};

전체 예제를 확인하세요.

Kinesis 스트림

Kinesis 스트림을 생성하고 함수로 구독합니다.

sst.config.ts
const stream = new sst.aws.KinesisStream("MyStream");
// 모든 이벤트를 구독하는 함수 생성
stream.subscribe("AllSub", "subscriber.all");
// `bar` 타입의 이벤트만 구독하는 함수 생성
stream.subscribe("FilteredSub", "subscriber.filtered", {
filters: [
{
data: {
type: ["bar"],
},
},
],
});
const app = new sst.aws.Function("MyApp", {
handler: "publisher.handler",
link: [stream],
url: true,
});
return {
app: app.url,
stream: stream.name,
};

전체 예제를 확인하세요.

AWS Lambda Go

이 예제는 Lambda 함수에서 go 런타임을 사용하는 방법을 보여줍니다.

Go 함수는 src 디렉토리에 있으며, 함수에서 이를 가리킵니다.

sst.config.ts
new sst.aws.Function("MyFunction", {
url: true,
runtime: "go",
link: [bucket],
handler: "./src",
});

또한 S3 버킷에 연결합니다. 함수에서 이 버킷을 참조할 수 있습니다.

src/main.go
func handler() (string, error) {
bucket, err := resource.Get("MyBucket", "name")
if err != nil {
return "", err
}
return bucket.(string), nil
}

resource.Get 함수는 SST Go SDK에서 제공됩니다.

src/main.go
import (
"github.com/sst/sst/v3/sdk/golang/resource"
)

sst dev CLI는 Go 함수를 Live로 실행하는 것도 지원합니다.

sst.config.ts
const bucket = new sst.aws.Bucket("MyBucket");
new sst.aws.Function("MyFunction", {
url: true,
runtime: "go",
link: [bucket],
handler: "./src",
});

전체 예제를 확인하세요.

AWS Lambda 재시도와 큐 사용

SQS 큐를 사용해 Lambda 호출을 재시도하는 방법에 대한 예제입니다.

Lambda 함수의 목적지로 설정될 SQS 재시도 큐를 생성합니다.

src/retry.ts
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], // 함수가 재시도 큐에 메시지를 보낼 수 있도록 연결
});
new aws.lambda.FunctionEventInvokeConfig("eventConfig", {
functionName: $resolve([busSubscriber.nodes.function.name]).apply(
([name]) => name,
),
maximumRetryAttempts: 2, // 기본값은 2, 0에서 2 사이여야 함
destinationConfig: {
onFailure: {
destination: retryQueue.arn,
},
},
});

버스에 메시지를 발행할 버스 구독자를 생성합니다. 계속 실패하는 메시지를 위한 DLQ(Dead Letter Queue)를 포함합니다.

sst.config.ts
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().name}:${
aws.getCallerIdentityOutput().accountId
}:function:*`,
],
},
],
transform: {
function: {
deadLetterConfig: {
targetArn: dlq.arn,
},
},
},
});

재시도 함수는 메시지를 읽고 백오프와 함께 큐로 다시 보내 재시도합니다.

src/retry.ts
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`);
// 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;
}
}
}
};
sst.config.ts
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], // 함수가 큐에 메시지를 보낼 수 있도록 연결
});
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,
},
permissions: [
{
actions: ["lambda:GetFunction", "lambda:InvokeFunction"],
resources: [
$interpolate`arn:aws:lambda:${aws.getRegionOutput().name}:${
aws.getCallerIdentityOutput().accountId
}:function:*`,
],
},
],
transform: {
function: {
deadLetterConfig: {
targetArn: dlq.arn,
},
},
},
});
return {
publisher: publisher.url,
dlq: dlq.url,
retryQueue: retryQueue.url,
};

전체 예제 보기.

AWS Lambda 스트리밍

Lambda 함수에서 스트리밍을 활성화하는 방법에 대한 예제입니다.

sst.config.ts
{
streaming: true
}

sst dev는 스트리밍을 지원하지 않지만, 로컬에서 테스트하기 위해 lambda-stream 패키지를 사용할 수 있습니다.

Terminal window
npm install lambda-stream

그런 다음, streamifyResponse 함수를 사용해 핸들러를 감쌀 수 있습니다:

index.ts
import { APIGatewayProxyEventV2 } from "aws-lambda";
import { streamifyResponse, ResponseStream } from "lambda-stream";
export const handler = streamifyResponse(myHandler);
async function myHandler(
_event: APIGatewayProxyEventV2,
responseStream: ResponseStream
): Promise<void> {
return new Promise((resolve, _reject) => {
responseStream.setContentType('text/plain')
responseStream.write('Hello')
setTimeout(() => {
responseStream.write(' World')
responseStream.end()
resolve()
}, 3000)
})
}

배포 시, 이 코드는 awslambda.streamifyResponse를 사용합니다.

터미널에서 이 기능을 테스트하려면, curl 명령어에 --no-buffer 옵션을 사용하세요.

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

여기서는 API Gateway가 스트리밍을 지원하지 않기 때문에 Function URL을 직접 사용합니다.

sst.config.ts
const fn = new sst.aws.Function("MyFunction", {
url: true,
streaming: true,
timeout: "15 minutes",
handler: "index.handler",
});
return {
url: fn.url,
};

전체 예제를 확인하세요.

VPC 내의 AWS Lambda

SST를 사용하여 VPC 내에 있는 Lambda 함수를 로컬에서 작업할 수 있습니다. 이를 위해 Vpc 컴포넌트에서 bastionnat을 활성화해야 합니다.

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

NAT 게이트웨이는 Lambda 함수가 인터넷에 연결할 수 있도록 필요합니다. 반면, 베스천 호스트는 로컬 머신이 VPC로 터널링할 수 있도록 필요합니다.

이전에 설치하지 않았다면 터널을 설치해야 합니다.

Terminal window
sudo sst tunnel install

이 작업은 머신에 네트워크 인터페이스를 생성하기 위해 sudo 권한이 필요합니다. 이 작업은 한 번만 수행하면 됩니다.

이제 sst dev를 실행하면 함수가 VPC 내의 리소스에 접근할 수 있습니다. 예를 들어, 여기서는 Redis 클러스터에 연결합니다.

index.ts
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,
},
}
);

Redis 클러스터는 함수와 동일한 VPC 내에 있습니다.

sst.config.ts
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"
});
return {
url: api.url,
};

전체 예제를 확인하세요.

AWS 다중 리전

여러 AWS 리전에 리소스를 배포하려면, 배포할 리전에 대한 새로운 프로바이더를 생성하면 됩니다.

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

그런 다음 해당 프로바이더를 리소스에 전달합니다.

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

프로바이더가 전달되지 않으면 기본 프로바이더가 사용됩니다. 리전이 지정되지 않으면 자격 증명의 기본 리전이 사용됩니다.

sst.config.ts
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 }
);
return {
east: east.url,
west: west.url,
};

전체 예제를 확인하세요.

AWS NestJS with Redis

NestJS와 Redis를 사용하여 조회수 카운터 앱을 만듭니다.

또한 Node 22.12가 설치되어 있는지 확인하세요. 또는 --experimental-require-module 플래그를 설정하세요. 이렇게 하면 NestJS가 SST SDK를 가져올 수 있습니다.

이 예제는 NestJS를 Fargate 서비스로 ECS에 배포하고 Redis와 연결합니다.

sst.config.ts
cluster.addService("MyService", {
link: [redis],
loadBalancer: {
ports: [{ listen: "80/http", forward: "3000/http" }],
},
dev: {
command: "npm run start:dev",
},
});

Redis 클러스터가 VPC에 있기 때문에 로컬 머신에서 연결하려면 터널이 필요합니다.

Terminal window
sudo npx sst tunnel install

이 명령은 머신에 네트워크 인터페이스를 생성하기 위해 sudo 권한이 필요합니다. 이 작업은 머신에서 한 번만 수행하면 됩니다.

앱을 로컬에서 실행하려면 다음 명령을 실행하세요.

Terminal window
npx sst dev

이제 http://localhost:3000으로 이동하면 페이지를 새로고침할 때마다 카운터가 업데이트되는 것을 볼 수 있습니다.

마지막으로, 예제에 포함된 Dockerfile을 사용하여 npx sst deploy --stage production 명령으로 배포할 수 있습니다.

sst.config.ts

전체 예제를 확인하세요.


AWS Next.js에 동작 추가하기

OpenNext로 AWS에 배포된 Next.js 앱의 CDN에 추가 라우트나 캐시 동작을 추가하는 방법입니다.

새로운 오리진으로 전달할 경로 패턴을 지정합니다. 예를 들어 /blog 경로로 들어오는 모든 요청을 다른 오리진으로 전달하려면 다음과 같이 설정합니다.

sst.config.ts
pathPattern: "/blog/*"

그리고 새로운 오리진의 도메인을 지정합니다.

sst.config.ts
domainName: "blog.example.com"

이 설정을 사용해 사이트의 CDN을 transform하여 추가 동작을 추가합니다.

sst.config.ts
const blogOrigin = {
// 새로운 오리진의 도메인
domainName: "blog.example.com",
originId: "blogCustomOrigin",
customOriginConfig: {
httpPort: 80,
httpsPort: 443,
originSslProtocols: ["TLSv1.2"],
// HTTPS 지원 여부
originProtocolPolicy: "https-only",
},
};
const cacheBehavior = {
// 새로운 오리진으로 전달할 경로
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]);
},
},
});

전체 예제를 확인하세요.

AWS Next.js 기본 인증

간단한 Next.js 앱을 배포하고 기본 인증을 추가합니다.

이 기능은 팀과 앱을 공유하지만 공개적으로 접근할 수 없도록 보호하려는 개발 환경에 유용합니다.

이 기능은 CloudFront 함수에 코드를 주입하여 기본 인증 헤더를 확인하고 USERNAMEPASSWORD 시크릿과 일치하는지 검사합니다.

sst.config.ts
{
injection: $interpolate`
if (
!event.request.headers.authorization
|| event.request.headers.authorization.value !== "Basic ${basicAuth}"
) {
return {
statusCode: 401,
headers: {
"www-authenticate": { value: "Basic" }
}
};
}`,
}

이를 배포하려면 먼저 USERNAMEPASSWORD 시크릿을 설정해야 합니다.

Terminal window
sst secret set USERNAME my-username
sst secret set PASSWORD my-password

프리뷰 환경에 배포하는 경우 --fallback 플래그를 사용하여 시크릿을 설정할 수 있습니다.

sst.config.ts
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: {
// 프로덕션 환경에서는 비밀번호 보호하지 않음
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,
},
});

전체 예제를 확인하세요.

AWS Next.js 컨테이너와 Redis

Next.js와 Redis를 사용하여 히트 카운터 앱을 만듭니다.

이 예제는 Next.js를 Fargate 서비스로 ECS에 배포하고 Redis와 연결합니다.

sst.config.ts
cluster.addService("MyService", {
link: [redis],
loadBalancer: {
ports: [{ listen: "80/http", forward: "3000/http" }],
},
dev: {
command: "npm run dev",
},
});

Redis 클러스터가 VPC에 있기 때문에 로컬 머신에서 연결하려면 터널이 필요합니다.

Terminal window
sudo npx sst tunnel install

이 명령은 머신에 네트워크 인터페이스를 생성하기 위해 sudo 권한이 필요합니다. 이 작업은 머신에서 한 번만 수행하면 됩니다.

앱을 로컬에서 실행하려면 다음 명령을 실행하세요.

Terminal window
npx sst dev

이제 http://localhost:3000으로 이동하면 페이지를 새로고침할 때마다 카운터가 업데이트되는 것을 확인할 수 있습니다.

마지막으로, 앱을 배포하려면 다음 단계를 따르세요.

  1. next.config.mjs 파일에 output: "standalone"을 설정합니다.
  2. 이 예제에 포함된 Dockerfile을 추가합니다.
  3. npx sst deploy --stage production을 실행합니다.
sst.config.ts
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 });
cluster.addService("MyService", {
link: [redis],
loadBalancer: {
ports: [{ listen: "80/http", forward: "3000/http" }],
},
dev: {
command: "npm run dev",
},
});

전체 예제를 확인하세요.

AWS Next.js 스트리밍

Next.js RSC(React Server Components) 스트리밍을 사용하는 예제입니다. 비동기 컴포넌트를 스트리밍하기 위해 Suspense를 사용합니다.

app/page.tsx
<Suspense fallback={<div>로딩 중...</div>}>
<Friends />
</Suspense>

이 데모를 위해 라우트가 정적으로 빌드되지 않도록 설정해야 합니다.

app/page.tsx
export const dynamic = "force-dynamic";

이 설정은 OpenNext로 배포되며, 스트리밍을 활성화하기 위해 설정이 필요합니다.

open-next.config.ts
export default {
default: {
override: {
wrapper: "aws-lambda-streaming"
}
}
};

3초 지연 후 friends 섹션이 로드되는 것을 확인할 수 있습니다.

Safari는 데이터를 스트리밍할 시점을 결정하기 위해 다른 휴리스틱을 사용합니다. 스트리밍을 트리거하기 위해 충분한 초기 HTML을 렌더링해야 합니다. 이는 일반적으로 데모 앱에서만 문제가 됩니다.

sst.config.ts
new sst.aws.Nextjs("MyWeb");

전체 예제를 확인하세요.

AWS Postgres 로컬

이 예제에서는 개발 환경에서 로컬로 실행 중인 Postgres 인스턴스에 연결합니다. 배포 시에는 RDS를 사용합니다.

Postgres를 로컬에서 실행하기 위해 docker run CLI를 사용합니다. Docker를 사용하지 않아도 되며, Postgres.app이나 다른 방법으로 Postgres를 로컬에서 실행할 수 있습니다.

Terminal window
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

데이터는 .sst/storage 디렉토리에 저장됩니다. 따라서 개발 서버를 다시 시작해도 데이터는 그대로 유지됩니다.

그런 다음 Postgres 컴포넌트의 dev 속성을 로컬 Postgres 인스턴스 설정으로 구성합니다.

sst.config.ts
dev: {
username: "postgres",
password: "password",
database: "local",
port: 5432,
}

Postgres에 dev 속성을 제공하면 SST는 sst dev를 실행할 때 새로운 RDS 데이터베이스를 배포하지 않고 로컬 Postgres 인스턴스를 사용합니다.

또한 로컬에서 실행 중인지 조건부로 확인하지 않고도 Resource link를 통해 데이터베이스에 접근할 수 있습니다.

index.ts
const pool = new Pool({
host: Resource.MyPostgres.host,
port: Resource.MyPostgres.port,
user: Resource.MyPostgres.username,
password: Resource.MyPostgres.password,
database: Resource.MyPostgres.database,
});

위 코드는 sst devsst deploy 모두에서 동작합니다.

sst.config.ts
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",
});

전체 예제 보기.

Lambda에서 Prisma 사용하기

Lambda 함수에서 Prisma를 사용하려면 다음 단계를 따라야 합니다.

  • 올바른 아키텍처로 Prisma Client 생성
  • 생성된 클라이언트를 함수로 복사
  • VPC 내에서 함수 실행

prisma/schema.prisma 파일에서 binaryTargets 옵션을 사용해 아키텍처를 설정할 수 있습니다.

prisma/schema.prisma
// x86용
binaryTargets = ["native", "rhel-openssl-3.0.x"]
// ARM용
// binaryTargets = ["native", "linux-arm64-openssl-3.0.x"]

ARM으로 전환할 수도 있습니다. 이 경우 sst.config.ts 파일에서 함수 아키텍처도 변경해야 합니다.

sst.config.ts
{
// ARM용
architecture: "arm64"
}

스키마를 변경할 때마다 prisma generate를 실행해 클라이언트를 생성해야 합니다.

이 작업은 매번 배포할 때마다 수행해야 하므로, package.jsonpostinstall 스크립트를 추가합니다.

package.json
"scripts": {
"postinstall": "prisma generate"
}

이렇게 하면 npm install 시 명령어가 실행됩니다.

그런 다음 배포할 때 생성된 클라이언트를 함수로 복사해야 합니다.

sst.config.ts
{
copyFiles: [{ from: "node_modules/.prisma/client/" }]
}

Prisma가 Data API를 지원하지 않기 때문에 함수는 VPC 내에서 실행되어야 합니다.

sst.config.ts
{
vpc
}

서버리스 환경에서의 Prisma

Prisma는 서버리스 환경에서 그리 좋지 않습니다. 몇 가지 이유가 있습니다:

  1. Data API를 지원하지 않아서 연결 풀을 직접 관리해야 합니다.
  2. Data API가 없으면 함수가 VPC 내부에서 실행되어야 합니다.
    • VPC에 연결하지 않고는 sst dev를 사용할 수 없습니다.
  3. 클라이언트의 내부 아키텍처로 인해 콜드 스타트가 느립니다.

대신 Drizzle 사용을 권장합니다. 이 예제는 이미 Prisma를 사용 중인 사람들을 위해 참고용으로 제공됩니다.

sst.config.ts
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],
// ARM 아키텍처 사용 시
// architecture: "arm64",
handler: "index.handler",
copyFiles: [{ from: "node_modules/.prisma/client/" }],
});
return {
api: api.url,
};

전체 예제 보기.

Lambda에서 Puppeteer 사용하기

Lambda 함수에서 Puppeteer를 사용하려면 다음이 필요합니다:

  1. puppeteer-core
  2. Chromium
    • sst dev에서는 로컬에 설치된 Chromium 버전을 사용합니다.
    • sst deploy에서는 @sparticuz/chromium 패키지를 사용합니다. 이 패키지는 Lambda용으로 미리 빌드된 바이너리를 포함하고 있습니다.

Chromium 버전

Puppeteer는 특정 버전의 Chromium을 선호하기 때문에, Puppeteer 버전이 지원하는 Chrome 버전을 확인해야 합니다. Puppeteer의 Chromium 지원 페이지로 이동하여 호환되는 버전을 확인하세요.

예를 들어, Puppeteer v23.1.1은 Chrome for Testing 127.0.6533.119을 지원합니다. 따라서 @sparticuz/chromium의 v127 버전을 사용합니다.

Terminal window
npm install puppeteer-core@23.1.1 @sparticuz/chromium@127.0.0

로컬에서 Chromium 설치하기

로컬에서 사용하려면 Chromium을 설치해야 합니다.

Terminal window
npx @puppeteer/browsers install chromium@latest --path /tmp/localChromium

설치가 완료되면 Chromium 바이너리의 위치를 확인할 수 있습니다. /tmp/localChromium/chromium/mac_arm-1350406/chrome-mac/Chromium.app/Contents/MacOS/Chromium.

이 경로를 Lambda 함수에서 업데이트하세요.

index.ts
// 로컬 Chromium 바이너리의 경로
const YOUR_LOCAL_CHROMIUM_PATH = "/tmp/localChromium/chromium/mac_arm-1350406/chrome-mac/Chromium.app/Contents/MacOS/Chromium";

SST_DEV 환경 변수를 사용하여 올바른 바이너리를 사용하고 있음을 확인할 수 있습니다.

index.ts
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,
});

배포

@sparticuz/chromium이 Lambda용으로 미리 빌드된 바이너리를 제공하기 때문에, 이를 배포하기 위해 별도의 레이어가 필요하지 않습니다.

nodejs.install에 설정만 해주면 됩니다.

sst.config.ts
{
nodejs: {
install: ["@sparticuz/chromium"]
}
}

배포 시 SST가 적절한 바이너리를 사용합니다.

Puppeteer를 실행하는 데 시간이 걸릴 수 있으므로, 함수에 더 많은 메모리와 긴 타임아웃을 설정합니다.

sst.config.ts
const api = new sst.aws.Function("MyFunction", {
url: true,
memory: "2 GB",
timeout: "15 minutes",
handler: "index.handler",
nodejs: {
install: ["@sparticuz/chromium"],
},
});
return {
url: api.url,
};

전체 예제를 확인하세요.


queues_mPz98EoDH3dYc2gPzeNZTf 구독하기

SQS 큐를 생성하고, 이를 구독하며, 함수에서 큐에 메시지를 발행합니다.

sst.config.ts
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,
});
return {
app: app.url,
queue: queue.url,
};

전체 예제 보기.

AWS Redis 로컬 설정

이 예제에서는 개발 환경에서 로컬 Docker Redis 인스턴스에 연결합니다. 배포 시에는 Redis ElastiCache를 사용합니다.

로컬 Redis 서버를 시작하기 위해 docker run 커맨드라인 인터페이스를 사용합니다. Docker를 사용하지 않아도 되며, 원하는 방식으로 로컬에서 실행할 수 있습니다.

Terminal window
docker run \
--rm \
-p 6379:6379 \
-v $(pwd)/.sst/storage/redis:/data \
redis:latest

데이터는 .sst/storage 디렉토리에 저장됩니다. 따라서 개발 서버를 재시작해도 데이터가 유지됩니다.

그런 다음 Redis 컴포넌트의 dev 속성을 로컬 Redis 서버 설정으로 구성합니다.

sst.config.ts
dev: {
host: "localhost",
port: 6379
}

dev 속성을 제공하면 SST는 sst dev를 실행할 때 로컬 Redis 서버를 사용하고 새로운 Redis ElastiCache 클러스터를 배포하지 않습니다.

또한 리소스 link를 통해 Redis에 접근할 수 있습니다.

index.ts
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,
},
},
);

로컬 Redis 서버는 standalone 모드로 실행되며, 배포 시에는 cluster 모드로 실행됩니다. 따라서 Lambda 함수는 올바른 설정으로 연결해야 합니다.

sst.config.ts
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",
});

전체 예제를 확인하세요.

AWS Remix 컨테이너와 Redis

Remix와 Redis를 사용하여 히트 카운터 앱을 만듭니다.

이 예제는 Remix를 Fargate 서비스로 ECS에 배포하고 Redis와 연결합니다.

sst.config.ts
cluster.addService("MyService", {
link: [redis],
loadBalancer: {
ports: [{ listen: "80/http", forward: "3000/http" }],
},
dev: {
command: "npm run dev",
},
});

Redis 클러스터가 VPC 내에 있기 때문에, 로컬 머신에서 연결하려면 터널이 필요합니다.

Terminal window
sudo npx sst tunnel install

이 명령은 머신에 네트워크 인터페이스를 생성하기 위해 sudo 권한이 필요합니다. 이 작업은 머신에서 한 번만 수행하면 됩니다.

앱을 로컬에서 실행하려면 다음 명령을 실행하세요.

Terminal window
npx sst dev

이제 http://localhost:5173으로 접속하면 페이지를 새로고침할 때마다 카운터가 업데이트되는 것을 확인할 수 있습니다.

마지막으로, 이 예제에 포함된 Dockerfile을 추가하고 npx sst deploy --stage production을 실행하여 배포할 수 있습니다.

sst.config.ts
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 });
cluster.addService("MyService", {
link: [redis],
loadBalancer: {
ports: [{ listen: "80/http", forward: "3000/http" }],
},
dev: {
command: "npm run dev",
},
});

전체 예제를 확인하세요.

AWS Remix 스트리밍

Remix 스트리밍 가이드를 따라 데이터를 스트리밍하는 앱을 만듭니다.

defer 유틸리티를 사용하여 loader 함수를 통해 데이터를 스트리밍합니다.

app/routes/_index.tsx
return defer({
spongebob,
friends: friendsPromise,
});

그런 다음 SuspenseAwait 컴포넌트를 사용하여 데이터를 렌더링합니다.

app/routes/_index.tsx
<Suspense fallback={<div>Loading...</div>}>
<Await resolve={friends}>
{ /* ... */ }
</Await>
</Suspense>

3초 지연 후 friends 섹션이 로드되는 것을 확인할 수 있습니다.

Safari는 데이터를 스트리밍할 시점을 결정하기 위해 다른 휴리스틱을 사용합니다. 스트리밍을 트리거하려면 충분한 초기 HTML을 렌더링해야 합니다. 이는 일반적으로 데모 앱에서만 문제가 됩니다.

스트리밍은 Remix 컴포넌트와 함께 바로 작동합니다.

sst.config.ts
new sst.aws.Remix("MyWeb");

전체 예제를 확인하세요.

라우터와 버킷

주어진 버킷의 public 폴더에서 정적 파일을 제공하는 라우터를 생성합니다.

sst.config.ts
// CloudFront가 접근할 수 있는 버킷 생성
const bucket = new sst.aws.Bucket("MyBucket", {
access: "cloudfront",
});
// 이미지를 `public` 폴더에 업로드
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" },
},
},
});
return {
image: $interpolate`${router.url}/spongebob.svg`,
};

전체 예제 보기.

라우터와 함수 URL

모든 요청을 URL이 있는 함수로 라우팅하는 라우터를 생성합니다.

sst.config.ts
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", {
domain: "router.ion.dev.sst.dev",
routes: {
"/api/*": api.url,
"/*": $interpolate`https://${bucket.domain}`,
},
});
return {
router: router.url,
bucket: bucket.domain,
};

전체 예제 보기.

AWS 클러스터 서비스 디스커버리

이 예제에서는 AWS Cloud Map 서비스 호스트 이름을 사용해 클러스터에서 실행 중인 서비스에 연결합니다. 이는 서비스 디스커버리에 유용합니다.

VPC 내의 클러스터에 서비스를 배포하고, 서비스의 클라우드 맵 호스트 이름을 사용해 VPC 내에서 접근할 수 있습니다.

lambda.ts
const reponse = await fetch(`http://${Resource.MyService.service}`);

여기서는 서비스에 연결된 Lambda 함수를 통해 접근하며, 이 함수는 동일한 VPC에 배포됩니다.

sst.config.ts
const vpc = new sst.aws.Vpc("MyVpc", { nat: "ec2" });
const cluster = new sst.aws.Cluster("MyCluster", { vpc });
const service = cluster.addService("MyService");
new sst.aws.Function("MyFunction", {
vpc,
url: true,
link: [service],
handler: "lambda.handler",
});

전체 예제 보기.

Lambda에서 Sharp 사용하기

Sharp 라이브러리를 사용하여 이미지 크기를 조정합니다. 이 예제에서는 로컬 파일인 logo.png를 100x100 픽셀로 크기를 조정합니다.

sst.config.ts
{
nodejs: { install: ["sharp"] }
}

Lambda용으로 사전 빌드된 바이너리가 포함된 sharp를 사용하기 때문에 배포 시 레이어가 필요하지 않습니다. 이는 nodejs.install에서 처리됩니다.

개발 환경에서는 로컬에서 sharp npm 패키지를 사용합니다.

package.json
{
"dependencies": {
"sharp": "^0.33.5"
}
}

배포 시 SST는 대상 Lambda 아키텍처에 맞는 sharp 패키지의 바이너리를 사용합니다.

sst.config.ts
const func = new sst.aws.Function("MyFunction", {
url: true,
handler: "index.handler",
nodejs: { install: ["sharp"] },
copyFiles: [{ from: "logo.png" }],
});
return {
url: func.url,
};

전체 예제를 확인하세요.

AWS SolidStart WebSocket 엔드포인트

SolidStart 앱을 WebSocket 엔드포인트와 함께 컨테이너로 AWS에 배포합니다.

Nitro의 실험적 WebSocket 지원을 사용합니다.

app.config.ts
export default defineConfig({
server: {
experimental: {
websocket: true,
},
},
}).addRouter({
name: "ws",
type: "http",
handler: "./src/ws.ts",
target: "server",
base: "/ws",
});

배포 후 /ws 엔드포인트를 테스트하면 3초 지연 후 메시지를 반환합니다.

sst.config.ts
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });
const cluster = new sst.aws.Cluster("MyCluster", { vpc });
cluster.addService("MyService", {
loadBalancer: {
ports: [{ listen: "80/http", forward: "3000/http" }],
},
dev: {
command: "npm run dev",
},
});

전체 예제를 확인하세요.

AWS 정적 사이트 기본 인증

이 예제는 간단한 정적 사이트를 배포하고 기본 인증을 추가합니다.

개발 환경에서 팀과 정적 사이트를 공유하지만 공개적으로 접근할 수 없도록 보호하려는 경우에 유용합니다.

이 기능은 CloudFront 함수에 코드를 주입하여 기본 인증 헤더를 확인하고 USERNAMEPASSWORD 시크릿과 일치하는지 검사합니다.

sst.config.ts
{
injection: $interpolate`
if (
!event.request.headers.authorization
|| event.request.headers.authorization.value !== "Basic ${basicAuth}"
) {
return {
statusCode: 401,
headers: {
"www-authenticate": { value: "Basic" }
}
};
}`,
}

이를 배포하려면 먼저 USERNAMEPASSWORD 시크릿을 설정해야 합니다.

Terminal window
sst secret set USERNAME my-username
sst secret set PASSWORD my-password

프리뷰 환경에 배포하는 경우 --fallback 플래그를 사용하여 시크릿을 설정할 수 있습니다.

sst.config.ts
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",
// 프로덕션 환경에서는 비밀번호 보호하지 않음
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,
});

전체 예제 보기.

AWS 정적 사이트

S3와 CloudFront를 사용하여 간단한 HTML 파일을 정적 사이트로 배포합니다. 웹사이트는 site/ 디렉토리에 저장됩니다.

sst.config.ts
new sst.aws.StaticSite("MySite", {
path: "site",
});

전체 예제 보기.

AWS SvelteKit 컨테이너와 Redis

SvelteKit과 Redis를 사용하여 조회수 카운터 앱을 만듭니다.

이 예제는 SvelteKit을 Fargate 서비스로 ECS에 배포하고 Redis와 연결합니다.

sst.config.ts
cluster.addService("MyService", {
link: [redis],
loadBalancer: {
ports: [{ listen: "80/http", forward: "3000/http" }],
},
dev: {
command: "npm run dev",
},
});

Redis 클러스터가 VPC에 있기 때문에 로컬 머신에서 연결하려면 터널이 필요합니다.

Terminal window
sudo npx sst tunnel install

이 명령은 머신에 네트워크 인터페이스를 생성하기 위해 sudo 권한이 필요합니다. 이 작업은 머신에서 한 번만 수행하면 됩니다.

앱을 로컬에서 실행하려면 다음 명령을 실행하세요.

Terminal window
npx sst dev

이제 http://localhost:5173으로 이동하면 페이지를 새로고침할 때마다 카운터가 업데이트되는 것을 확인할 수 있습니다.

마지막으로, 이 예제에 포함된 Dockerfile을 추가하고 npx sst deploy --stage production을 실행하여 배포할 수 있습니다.

sst.config.ts
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 });
cluster.addService("MyService", {
link: [redis],
loadBalancer: {
ports: [{ listen: "80/http", forward: "3000/http" }],
},
dev: {
command: "npm run dev",
},
});

전체 예제를 확인하세요.

Swift in Lambda

al2023 런타임을 사용하여 간단한 Swift 애플리케이션을 Lambda에 배포합니다.

자세한 내용은 저장소의 README를 확인하세요.

sst.config.ts
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",
});
return {
url: router.url,
};

전체 예제 보기.

AWS에서 T3 스택 사용하기

Drizzle과 Postgres를 사용해 T3 스택을 AWS에 배포해 보겠습니다.

이 예제는 create-t3-app을 사용해 생성되었으며, tRPC, Drizzle, 인증 없음, Tailwind, Postgres, 그리고 App Router 옵션을 선택했습니다.

로컬 데이터베이스 대신 RDS Postgres 데이터베이스를 사용할 것입니다.

src/server/db/index.ts
const pool = new Pool({
host: Resource.MyPostgres.host,
port: Resource.MyPostgres.port,
user: Resource.MyPostgres.username,
password: Resource.MyPostgres.password,
database: Resource.MyPostgres.database,
});

Drizzle Kit도 비슷하게 설정합니다.

drizzle.config.ts
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;

Next.js 앱에서는 링크를 통해 Postgres 데이터베이스에 접근할 수 있습니다. .env 파일을 사용할 필요가 없습니다.

sst.config.ts
const rds = new sst.aws.Postgres("MyPostgres", { vpc, proxy: true });
new sst.aws.Nextjs("MyWeb", {
vpc,
link: [rds]
});

개발 모드로 실행하려면 다음 명령어를 입력하세요.

Terminal window
npm install
npx sst dev

데이터베이스와 VPC를 배포하는 데 몇 분 정도 걸립니다.

이 명령어는 로컬 머신이 RDS Postgres 데이터베이스에 연결할 수 있도록 터널을 시작합니다. 로컬 머신에 한 번만 설치하면 됩니다.

Terminal window
sudo npx sst tunnel install

이제 새로운 터미널에서 데이터베이스 마이그레이션을 실행할 수 있습니다.

Terminal window
npm run db:push

개발 모드에서는 Drizzle Studio가 자동으로 Studio 탭에서 시작됩니다.

sst.config.ts
new sst.x.DevCommand("Studio", {
link: [rds],
dev: {
command: "npx drizzle-kit studio",
},
});

자격 증명이 올바르게 사용되도록 package.jsonsst shell CLI로 업데이트합니다.

package.json
"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",

이제 npm run db:push를 실행하면 올바른 자격 증명으로 Drizzle Kit이 실행됩니다.

프로덕션 환경에 배포하려면 다음 명령어를 실행하세요.

Terminal window
npx sst deploy --stage production

그리고 마이그레이션을 실행합니다.

Terminal window
npx sst shell --stage production npx drizzle-kit push

로컬에서 실행 중이라면 터널이 실행 중인지 확인하세요.

Terminal window
npx sst tunnel --stage production

CI/CD 파이프라인에서 실행 중이라면 빌드 컨테이너가 동일한 VPC에 있어야 합니다.

sst.config.ts
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",
},
});

전체 예제를 확인해 보세요.

AWS Task Cron

TaskCron 컴포넌트를 사용하여 장기 실행 백그라운드 작업을 처리할 수 있습니다.

index.mjs에서 실행하려는 노드 스크립트가 있다고 가정해 보겠습니다. 이 스크립트는 Dockerfile을 사용하여 도커 컨테이너로 배포됩니다.

이 작업은 2분마다 실행되는 크론 작업에 의해 호출됩니다.

sst.config.ts
new sst.aws.Cron("MyCron", {
task,
schedule: "rate(2 minutes)"
});

sst dev에서 실행할 때는 dev.command를 사용하여 로컬에서 작업이 실행됩니다.

sst.config.ts
dev: {
command: "node index.mjs"
}

배포하려면 도커 데몬이 실행 중이어야 합니다.

sst.config.ts
const bucket = new sst.aws.Bucket("MyBucket");
const vpc = new sst.aws.Vpc("MyVpc");
const cluster = new sst.aws.Cluster("MyCluster", { vpc });
const task = cluster.addTask("MyTask", {
link: [bucket],
dev: {
command: "node index.mjs",
},
});
new sst.aws.Cron("MyCron", {
task,
schedule: "rate(2 minutes)",
});

전체 예제를 확인해 보세요.

AWS Task

백그라운드 작업을 실행하려면 Task 컴포넌트를 사용하세요.

image/index.mjs에서 실행하려는 노드 스크립트가 있습니다. 이 스크립트는 image/Dockerfile을 사용해 도커 컨테이너로 배포됩니다.

또한 작업과 연결된 함수가 있습니다. 이 함수는 SDK를 사용해 작업을 시작합니다.

index.ts
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),
};
};

sst dev에서 실행할 때는 dev.command를 사용해 로컬에서 작업이 실행됩니다.

sst.config.ts
dev: {
command: "node index.mjs"
}

배포하려면 도커 데몬이 실행 중이어야 합니다.

sst.config.ts
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 = cluster.addTask("MyTask", {
link: [bucket],
image: {
context: "image",
},
dev: {
command: "node index.mjs",
},
});
new sst.aws.Function("MyApp", {
vpc,
url: true,
link: [task],
handler: "index.handler",
});

전체 예제를 확인하세요.

토픽 구독하기

SNS 토픽을 생성하고, 함수에서 토픽에 메시지를 발행한 후, 함수와 큐를 통해 토픽을 구독합니다.

sst.config.ts
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,
});
return {
app: app.url,
topic: topic.name,
};

전체 예제 보기.

벡터 검색

Vector 컴포넌트를 사용해 벡터 데이터를 저장하고 검색할 수 있습니다. LLM을 활용해 일부 영화와 선택적으로 포스터에 대한 임베딩을 생성하는 시더 API가 포함되어 있습니다.

시딩이 완료되면 벡터 데이터베이스를 쿼리하기 위해 검색 API를 호출할 수 있습니다.

sst.config.ts
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,
});
return { seeder: seeder.url, app: app.url };

전체 예제 보기.

Vite로 React SPA 배포하기

Vite를 사용해 React 싱글 페이지 애플리케이션(SPA)을 S3와 CloudFront에 배포합니다.

sst.config.ts
new sst.aws.StaticSite("Web", {
build: {
command: "pnpm run build",
output: "dist",
},
});

전체 예제 보기

Cloudflare Cron

이 예제는 일정에 따라 실행되는 Cloudflare Worker를 만드는 방법을 보여줍니다.

sst.config.ts
const cron = new sst.cloudflare.Cron("Cron", {
job: "index.ts",
schedules: ["* * * * *"]
});
return {};

전체 예제 보기.

Cloudflare KV

이 예제는 Cloudflare KV 네임스페이스를 생성하고 이를 워커에 연결합니다. 이제 워커에서 SDK를 사용해 KV 네임스페이스와 상호작용할 수 있습니다.

sst.config.ts
const storage = new sst.cloudflare.Kv("MyStorage");
const worker = new sst.cloudflare.Worker("Worker", {
url: true,
link: [storage],
handler: "index.ts",
});
return {
url: worker.url,
};

전체 예제 보기

여러 개의 시크릿 연결하기

여러분의 앱에서 여러 개의 시크릿을 사용해야 할 수 있습니다. 새로운 시크릿을 만들고 각 함수나 리소스에 연결하는 것은 번거로울 수 있습니다.

이를 해결하기 위한 일반적인 패턴은 모든 시크릿을 객체로 관리한 후 한 번에 연결하는 것입니다. 이제 새로운 시크릿이 생기면 객체에 추가하기만 하면 모든 리소스에서 자동으로 사용할 수 있습니다.

sst.config.ts
// 모든 시크릿을 함께 관리
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,
});
return {
url: api.url,
};

전체 예제 보기.

기본 함수 프로퍼티 설정

앱 내 모든 함수에 기본 프로퍼티를 설정하려면 전역 $transform을 사용하세요.

sst.config.ts
$transform(sst.aws.Function, (args) => {
args.runtime = "nodejs14.x";
args.environment = {
FOO: "BAR",
};
});
new sst.aws.Function("MyFunction", {
handler: "index.ts",
});

전체 예제 보기

Vercel 도메인

Vercel 계정을 통해 구매하고 호스팅하는 도메인을 사용하는 라우터를 생성합니다. VERCEL_API_TOKENVERCEL_TEAM_ID 환경 변수가 설정되어 있는지 확인하세요.

sst.config.ts
const router = new sst.aws.Router("MyRouter", {
domain: {
name: "ion.sst.moe",
dns: sst.vercel.dns({ domain: "sst.moe" }),
},
routes: {
"/*": "https://sst.dev",
},
});
return {
router: router.url,
};

전체 예제를 확인하세요.