Skip to content

Building Fargate - Prelude - The Idea and the Process

Published: at 04:00 PM

Fargate Welcome Page

tldr;

I built a small private site called Fargate where my friends sign in and open files I’ve published. This prelude covers why I built it, how the pieces fit together, and how I worked with an agent to go from a spec to a deployed app in about three days (note that it didn’t actually take three full days, I worked on it here and there when I had time to spare). The rest of the series goes deeper on the app, Terraform, and Cloudflare.

The idea

My friends and I play board games online pretty regularly on Board Game Arena. I’ve started generating game guides with AI so that we can focus on gameplay while learning new rules. Sharing these files started becoming burdensome especially if I make updates to the guide over time. I figured I could probably build something with AI over the weekend and have it ready for our next gaming session.

What I wanted was pretty simple. A friend visits a site, enters their email, gets a one-time code, and sees a list of everything I’ve shared. They click a file and it opens. What I didn’t want was to deal with authentication that required them to create yet another password. I wanted things things to be almost friction free.

About the name

Before anybody asks, the name has nothing to do with AWS Fargate. In fact, it runs on Lambda.

I wanted a name for a place where my friends could reach into a web app and grab what they need. My first thought was something like “portal,” but that felt too generic. I’m also reading Abaddon’s Gate from The Expanse series right now and I liked the word “gate.” My friends are far away, so I mashed the two together and got Fargate.

Yes, I know it’s also an AWS product. That’s also why the welcome page is a 16-bit scene of an astronaut standing on a moon next to a stone portal.

What I didn’t want

The list of things I didn’t want ended up being just as important as the list of things I did:

What I did want was for it be as close to $0 a month to run.

How it fits together

A picture is worth a thousand words so:

Browser
   |
   v
Cloudflare ---------> handles DNS and sign-in, and keeps the bots out
   |
   v
API Gateway --------> only accepts requests that came through Cloudflare
   |
   v
Lambda -------------> the app: checks the sign-in token, lists files, makes links
   |
   v
S3 -----------------> a private bucket that holds the files

A friend’s browser talks to Cloudflare first. Cloudflare handles sign-in, so the app never sees a password or a login code. API Gateway (the AWS service) sits in front of the app and turns away anything that didn’t come through Cloudflare. The app runs on Lambda and can only list and read files. S3 holds everything and stays private.

When a friend opens a file, the app makes a short-lived link for that one file and the browser downloads it straight from S3. Files never pass through the app.

I’ll create articles for each piece separately. Part one covers the app, part two covers the Terraform that defines all of it, Cloudflare settings included, and part three covers sign-in with Cloudflare.

How it got built

I didn’t write most of the code. An agent did, and I reviewed every line of it. If you’ve read my unvibe and teach-diff post, you know I like keeping agents on a pretty tight leash especially when there is authentication involved across multiple service domains. Most of my work on this project went into deciding what to build and writing it down clearly enough that the agent couldn’t build something with security holes in it.

A PRD that got thrown out

I started with a product requirements doc. It got a lot right. It already picked Cloudflare Access for sign-in, said no database and no Rails, and had that “use configuration instead of code” rule in it.

What it didn’t do was answer the hard questions. Where does this run? It listed six acceptable places to deploy it, from Render to a small VPS, and said the runtime would be Bun or Node “depending on the chosen host.” Where does DNS live? “If Cloudflare manages DNS.” That’s fine for a first draft, but an agent that’s handed open questions like those will answer them for you, and you might not like the answers.

A spec that answered the questions

So two days later the PRD was replaced by a spec. The spec patches open holes in the PRD. It runs on Lambda. DNS moves to Cloudflare. It also defines every page and route, what the logs can and can’t contain, the tests, and a checklist of acceptance criteria.

Before any code, I also had a clickable prototype of the site. The spec says the real welcome page has to match it on desktop and on a phone, which turned “make it look cool” into something I could actually check.

Decision records

The three decisions that took real thought each got a short decision record:

Each one is a few paragraphs covering the choice, the options I didn’t pick, and the consequences. They’re mostly for future me, so that in six months I don’t re-argue something I already settled. They also mean the agent knows why things are the way they are, and doesn’t “fix” them.

A glossary

Borrowing the ubiquitous language concept from Domain Driven Design, I create a project glossary. The people are Friends, not users. The things they open are Files, not documents or assets. A short-lived address to a File is a Link, not a presigned URL. Each entry also lists the words to avoid.

Agents tend to drift between synonyms. Without a glossary you end up with user in one file, member in another, and account in a third, all meaning the same thing. With one, the code, the docs, and my conversations with the agent all use the same words.

Small issues, small pull requests

The spec got broken up into more than a dozen Linear issues, and most issues got split into a handful of stacked pull requests. Listing the bucket, sorting files, parsing keys, making links, and the error pages were each their own PR under one issue, for example.

That added up to 33 pull requests in the app repo and 14 in the infrastructure repo. The first code PR merged the day after the spec was agreed and the deploy workflow merged two days after that. Every change went through a pull request, nothing was pushed straight to main, and I reviewed every one.

I think most people dread seeing an agent written PR with a bazillion lines of changes in it. This is unreadable to me and I’d just end up rubber-stamping it. A 40-line PR that does one thing is something I can actually review.

Plan, then apply

Infrastructure got one extra rule. The agent runs terraform plan and explains what it’s going to do. I run terraform apply. I cover that in more detail in part two.

Why work this way?

Agents write code a lot faster than I can read it. In my Fable review I watched a model miss a data migration that a seasoned engineer would have flagged right away. Writing code faster doesn’t make that kind of mistake go away.

Coming up

In part one, I’ll get into the app itself. That covers why it’s TypeScript, why Bun runs locally and Node runs in production, and how a bucket can stand in for a database.


Next Post
Ruby C2PA 0.6.0 is Out