Skip to content

Bun on AWS with SST

Create and deploy a Bun app to AWS with SST.

We are going to build a hit counter using Bun and Redis. We’ll then deploy it to AWS in a container using SST.

Before you get started, make sure to configure your AWS credentials.


1. Create a project

Let’s start by creating our Bun app.

Terminal window
mkdir aws-bun && cd aws-bun
bun init -y

Init Bun Serve

Replace your index.ts with the following.

index.ts
const server = Bun.serve({
async fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/" && req.method === "GET") {
return new Response("Hello World!");
}
return new Response("404!");
},
});
console.log(`Listening on ${server.url}`);

This starts up an HTTP server by default on port 3000.


Add scripts

Add the following to your package.json.

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

This adds a dev script with a watcher and a build script that we’ll use later.


Init SST

Now let’s initialize SST in our app.

Terminal window
bunx sst init
bun install

This’ll create an sst.config.ts file in your project root and install SST.


2. Add a Cluster

To deploy our Bun app, let’s add an AWS Fargate container with Amazon ECS. Update your sst.config.ts.

sst.config.ts
async run() {
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: "bun dev",
},
});
}

This creates a VPC with a bastion host, an ECS Cluster, and adds a Fargate service to it.

The dev.command tells SST to instead run our Bun app locally in dev mode.


3. Add Redis

Let’s add an Amazon ElastiCache Redis cluster. Add this below the Vpc component in your sst.config.ts.

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

This shares the same VPC as our ECS cluster.


Now, link the Redis cluster to the container.

sst.config.ts
cluster.addService("MyService", {
// ...
link: [redis],
});

This will allow us to reference the Redis cluster in our Bun app.


Install a tunnel

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

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


Start dev mode

Start your app in dev mode.

Terminal window
bun sst dev

This will deploy your app, start a tunnel in the Tunnel tab, and run your Bun app locally in the MyServiceDev tab.


4. Connect to Redis

We want the / route of our API to increment a counter in our Redis cluster. Let’s start by installing the npm package we’ll use.

Terminal window
bun install ioredis

Add the relevant imports to your index.ts.

index.ts
import { Resource } from "sst";
import { Cluster } from "ioredis";
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,
},
}
);

Let’s update the / route.

index.ts
if (url.pathname === "/" && req.method === "GET") {
const counter = await redis.incr("counter");
return new Response(`Hit counter: ${counter}`);
}

Test your app

Let’s head over to http://localhost:3000 in your browser and it’ll show the current hit counter.

You should see it increment every time you refresh the page.


5. Deploy your app

To deploy our app we’ll first add a Dockerfile. This is building our app by running our build script from above.

Dockerfile
# use the official Bun image
# see all versions at https://hub.docker.com/r/oven/bun/tags
FROM oven/bun:1 AS base
WORKDIR /usr/src/app
# install dependencies into temp directory
# this will cache them and speed up future builds
FROM base AS install
RUN mkdir -p /temp/dev
COPY package.json bun.lockb /temp/dev/
RUN cd /temp/dev && bun install --frozen-lockfile
# install with --production (exclude devDependencies)
RUN mkdir -p /temp/prod
COPY package.json bun.lockb /temp/prod/
RUN cd /temp/prod && bun install --frozen-lockfile --production
# copy node_modules from temp directory
# then copy all (non-ignored) project files into the image
FROM base AS prerelease
COPY --from=install /temp/dev/node_modules node_modules
COPY . .
# [optional] tests & build
ENV NODE_ENV=production
# RUN bun test
RUN bun run build
# copy production dependencies and source code into final image
FROM base AS release
COPY --from=install /temp/prod/node_modules node_modules
COPY --from=prerelease /usr/src/app/index.ts .
COPY --from=prerelease /usr/src/app/package.json .
# run the app
USER bun
EXPOSE 3000/tcp
ENTRYPOINT [ "bun", "run", "index.ts" ]

This is pretty much the same setup from the Bun docs. We are just skipping running the tests.

Let’s also add a .dockerignore file in the root.

.dockerignore
node_modules
.git
.gitignore
README.md
Dockerfile*

Now to build our Docker image and deploy we run:

Terminal window
bun sst deploy --stage production

You can use any stage name here but it’s good to create a new stage for production. This’ll give the URL of your Bun app deployed as a Fargate service.

Terminal window
Complete
MyService: http://prod-MyServiceLoadBalanc-491430065.us-east-1.elb.amazonaws.com

Connect the console

As a next step, you can setup the SST Console to git push to deploy your app and monitor it for any issues.

SST Console Autodeploy

You can create a free account and connect it to your AWS account.