How I Shipped FileStream in a Weekend
Live video review for our team. Hetzner box, Docker Compose, FastAPI plus Next.js plus Celery, deployed and earning its keep by Sunday night. The actual playbook, the real prompts, the parts that broke.
The problem we had on Friday
The Cadence team reviews ad creative all day. The bottleneck wasn't the review, it was the round-trip. Slack threads, dropped Loom links, "wait can you re-upload that," six tabs open per creative.
I needed one URL where anyone on the team could drop a video, anyone else could scrub it, leave timestamped comments, and the whole thing would be searchable a month later.
By Friday night I'd named it FileStream and written the spec on a napkin.
How to use this
- 01
Don't start with the framework decision. Start with the one screen the user will stare at the most.
- 02
Use Prompt 1 to generate the schema and the route map. Lock that before any UI.
- 03
Spin up a Hetzner CX22 (€4/mo, 4 vCPU, 8GB) and put the whole thing behind Docker Compose. Don't touch Kubernetes. You don't need it.
- 04
Use Prompt 2 to generate the player + comment pane in one shot. Iterate from there.
- 05
Deploy on Sunday morning. Use the rest of the day to remove every feature you don't need.
The stack
Boring on purpose. Every one of these is something I've shipped before, so the only new thing I'm fighting is the product.
| Layer | Choice | Why |
|---|---|---|
| Frontend | Next.js 16 + RSC | Same stack as everything else. Zero context switch. |
| Backend | FastAPI | Fastest path from def to a live route. |
| Queue | Celery + Redis | Video transcoding without blocking the request. |
| Storage | S3-compatible (Backblaze B2) | $5/TB. Don't pay for AWS egress. |
| Host | Hetzner CX22 + Docker Compose | One box. One docker compose up -d. |
| TLS | Caddy | Let's Encrypt without thinking about it. |
The whole docker-compose.yml is 64 lines.
Prompt 1, the schema and route map
This is the prompt I ran first. It shaped every file that came after.
You are a senior backend engineer. I am building a video review tool called FileStream. Internal team use, expected scale: 50 concurrent users, 5,000 videos in year 1.
Core flows:
- Anyone on the team can upload a video (up to 2GB)
- Each upload gets a unique shareable URL
- Anyone with the URL can scrub the video and leave timestamped comments
- Comments thread; replies are flat (no nesting beyond one level)
- A weekly digest emails each user the videos they were @-mentioned in
Output exactly two artifacts.
ARTIFACT 1 — POSTGRES SCHEMA
Write the SQL for every table I need. Use UUIDs as primary keys. Add the indexes I will actually need at this scale, no more. Use snake_case. Add a one-line comment above each table explaining what it stores.
ARTIFACT 2 — FASTAPI ROUTE MAP
Write the full route table as a markdown table with columns: METHOD, PATH, AUTH, RESPONSE, ONE-LINE PURPOSE. Group by resource. No code yet. Just the contract.
Rules:
- Do not invent features. Build only what the flows above require.
- No microservices. One FastAPI app.
- If a decision has two reasonable answers, pick one and move on. Tell me in one sentence at the end why you picked it.
- Keep the schema under 8 tables. If you can't, push back and tell me which flow needs to be cut.The "if you can't, push back" line is the one that earned its keep. The first run came back with 11 tables. I asked which flow we should cut, and the model came back with: "kill the digest emails for v1, you can rebuild the data from the comment table later." That was the right call. Saved me a day.
Prompt 2, the player and comment pane
Single screen, the only one that matters. Everything else is plumbing.
Build the FileStream review screen as a single Next.js page (App Router, RSC). One file, all components co-located. Layout: - Left 70% of viewport: HTML5 video player. No custom controls — use the native ones. Add a thin scrubber overlay underneath that shows comment dots at their timestamps. - Right 30%: comment pane. Sticky to viewport. Shows all comments sorted by timestamp ascending. - Each comment shows: avatar, name, timestamp (clickable to seek), comment body, optional one reply input. - Clicking a comment dot in the scrubber seeks the video AND highlights that comment in the right pane. - A new-comment box pinned to the bottom of the right pane that auto-attaches the current playhead time. Constraints: - Dark mode only. Use #000 background, white text, [BRAND_ACCENT] for active states. - Tailwind only. No CSS modules. - Use [API_BASE_URL] for the backend. Show me the exact fetch shape. - No client-side routing inside this page. Everything happens via state. - Keyboard: J/K to seek -5s/+5s, space to play/pause, C to focus the comment box. Generate the full file. After the file, list every assumption you made in 6 bullets or fewer. If something is genuinely ambiguous, ask me before you generate.
It came back with three questions instead of generating. That's the right move. I answered them, ran the prompt again, and shipped the file straight to production.
The parts that broke
A few honest things, in case you build something similar.
Celery + Docker Compose networking
The Celery worker couldn't reach Redis on first boot because I'd set REDIS_URL=redis://localhost instead of redis://redis. Compose service names are the hostnames. Lost 40 minutes on this one. Worth knowing.
Backblaze CORS
S3-compatible doesn't always mean CORS-compatible. Backblaze needs the bucket-level CORS rules set via their API, not their dashboard. Took longer than the rest of the storage layer combined.
The video tag's seekable lie
Chrome will report a video as seekable before the metadata loads. If you wire scrubbing on the first loadeddata, your first three seek attempts get swallowed. Fix: wait for loadedmetadata, then enable.
What it cost
| Line item | Cost |
|---|---|
| Hetzner CX22 | €4.51/month |
| Backblaze B2 (first 10GB free) | $0 |
| Domain (already had it) | $0 |
| Total weekend cost | About one nice dinner |
Two days, one box, one product the team uses every day now. The lesson isn't the stack. The lesson is that the cost of building the thing is now lower than the cost of writing the doc that explains why you'd want the thing.
So just build it.
// work with me
Want this stack inside your business?
I work with a small number of operators per quarter. If your problem looks like the ones I write about, let's talk.
Work with me