Skip to the index
Português

Portfolio API

This site's public api: a Go binary with SQLite on a free VPS, behind Caddy with automatic TLS. Content lives in git and is re-imported on every start; a release tests the bundle on a scratch database and rolls back on its own if the new version does not come up. It replaced a Next.js api on Postgres without changing any of the 51 compared responses, and used 14 MB of memory on its first release.

Go / SQLite / Caddy / systemd / GitHub Actions

Context
This site's api started as Next.js on Postgres, Prisma, and paid queue and e-mail services, designed for an admin that did not exist yet. I'm the only one who writes the content, and the only reader is the site itself.
Pressure
No new paid services. The api had to fit on a free VPS that already runs another service of mine, and a bad release could not take the site down.
Decision
A single Go binary with SQLite behind Caddy. Content lives in git and the database is derived from it: every start re-imports the bundle, so the content backup is the repository itself. The contract between the site's TypeScript and the api's Go is checked at three points: Zod at export, strict import in Go that rejects unknown fields and the wrong bundle version, and response validation in the client.
Execution
A release tests the bundle on a scratch database before switching versions, swaps the link atomically, waits for readiness and rolls back on its own if the new version doesn't come up. The server keeps three versions, which is why migrations only add: the previous binary must start on the already-migrated database. The database is copied every day and before every migration, and a copy only counts after it passes an integrity check. The service runs sandboxed by systemd, with no shell, no writes outside its own folder and a memory cap. The health check reads the database and answers 503 when it fails, separate from a liveness probe that touches nothing, and an external schedule checks readiness every hour.
Evidence
Before the switch, both apis answered the same 51 requests over the same content, and the responses came out identical apart from ids and creation dates. On every pull request, CI exports the real content and runs the Go tests with the race detector against it.
Impact
The site has its own backend at zero infrastructure cost, and publishing is one command that never leaves a broken version serving the site.