{"slug":"build-a-distributed-web-app-with-tako-and-cockroachdb","url":"https://tako.sh/blog/build-a-distributed-web-app-with-tako-and-cockroachdb/","canonical":"https://tako.sh/blog/build-a-distributed-web-app-with-tako-and-cockroachdb/","title":"Build a Distributed Web App with Tako and CockroachDB","date":"2026-04-10T06:18","description":"Three VPS boxes, Tako for the app layer, CockroachDB for the data layer — a fully distributed stack running on hardware you own, with no managed plan underneath.","author":null,"image":"a22179342d93","imageAlt":null,"headings":[{"depth":2,"slug":"why-cockroachdb","text":"Why CockroachDB"},{"depth":2,"slug":"the-topology","text":"The topology"},{"depth":2,"slug":"the-tako-side","text":"The Tako side"},{"depth":2,"slug":"making-reads-actually-fast","text":"Making reads actually fast"},{"depth":2,"slug":"who-does-what","text":"Who does what"},{"depth":2,"slug":"the-point","text":"The point"}],"markdown":"We've been telling the story of [building your own edge network on commodity hardware](/blog/build-your-own-edge-network-on-commodity-hardware) — three VPS boxes, [one `tako.toml`](/blog/one-config-many-servers), Cloudflare routing users to the nearest one. It's a nice story until your users start actually writing data. Because as soon as your database lives in one region, every query from Tokyo still round-trips to us-east-1 and the edge only got you halfway.\n\nThe other half is a database that can live on all three boxes at once. CockroachDB is one of the nicest answers to that we've found, and it pairs neatly with Tako.\n\n## Why CockroachDB\n\nIt speaks the PostgreSQL wire protocol, so your ORM doesn't know it's not Postgres. Underneath, it's a distributed, strongly consistent SQL database — nodes join a cluster, ranges are replicated via Raft, and each node can serve queries locally. You run one `cockroach` binary on three boxes and you have a single logical database that survives a whole region disappearing.\n\nIt's also a single self-hosted binary ([github.com/cockroachdb/cockroach](https://github.com/cockroachdb/cockroach)) that installs alongside anything else you already run. No managed tier required to get started.\n\n## The topology\n\n```d2\ndirection: right\n\nusers: Users {shape: rectangle; style.font-size: 14}\ncf: Cloudflare {shape: cloud; style.fill: \"#9BC4B6\"; style.font-size: 14}\n\nla: LA VPS {\n  style.fill: \"#FFF9F4\"\n  app: tako-server + app {shape: hexagon; style.fill: \"#E88783\"; style.font-size: 13}\n  db: cockroach {shape: cylinder; style.fill: \"#FFF9F4\"; style.font-size: 13}\n  app -> db: localhost:26257\n}\n\nfra: Frankfurt VPS {\n  style.fill: \"#FFF9F4\"\n  app: tako-server + app {shape: hexagon; style.fill: \"#E88783\"; style.font-size: 13}\n  db: cockroach {shape: cylinder; style.fill: \"#FFF9F4\"; style.font-size: 13}\n  app -> db: localhost:26257\n}\n\nsgp: Singapore VPS {\n  style.fill: \"#FFF9F4\"\n  app: tako-server + app {shape: hexagon; style.fill: \"#E88783\"; style.font-size: 13}\n  db: cockroach {shape: cylinder; style.fill: \"#FFF9F4\"; style.font-size: 13}\n  app -> db: localhost:26257\n}\n\nusers -> cf\ncf -> la.app: nearest\ncf -> fra.app: nearest\ncf -> sgp.app: nearest\n\nla.db <-> fra.db: raft\nfra.db <-> sgp.db: raft\nsgp.db <-> la.db: raft\n```\n\nEach VPS runs two things: `tako-server` — which runs your app, terminates TLS, and handles routing — and a `cockroach` node joined to the cluster. Your app connects to CockroachDB at `localhost:26257`, so every query goes to the local replica. CockroachDB handles cross-region replication and consensus in the background.\n\nTwo tools, one responsibility each, no control plane stacked on top. Tako runs the apps. CockroachDB runs as its own systemd service — started on each box with something like:\n\n```bash\ncockroach start \\\n  --certs-dir=/etc/cockroach/certs \\\n  --advertise-addr=<public-ip> \\\n  --join=la.example.com,fra.example.com,sgp.example.com \\\n  --locality=region=eu-central,zone=fra\n```\n\nThe `--locality` flag is what makes this multi-region instead of just \"three boxes running the same database\" — CockroachDB uses it for replica placement, follower reads, and its multi-region topology patterns.\n\n## The Tako side\n\nEverything Tako needs lives in one [`tako.toml`](/docs/tako-toml):\n\n```toml\nname = \"myapp\"\n\n[build]\nrun = \"bun run build\"\n\n[envs.production]\nroute = \"myapp.com\"\nservers = [\"la\", \"fra\", \"sgp\"]\n```\n\nAnd a single secret every server shares:\n\n```bash\ntako secrets set DATABASE_URL --env production\n# postgres://myapp@localhost:26257/myapp?sslmode=verify-full\ntako deploy\n```\n\nTako [builds the artifact once](/docs/deployment), SFTPs it to all three boxes in parallel, and each server runs its own rolling update. `DATABASE_URL` is [encrypted at rest and injected via fd 3](/blog/secrets-without-env-files) — no `.env` file, no plaintext on disk — and it's identical across regions because every node is reachable at `localhost`. Your app code never has to know which region it's running in.\n\n## Making reads actually fast\n\nHaving a local replica isn't magic on its own — writes still need quorum, and not every read is served locally by default. CockroachDB ships a few [multi-region topology patterns](https://www.cockroachlabs.com/docs) to tune that trade-off:\n\n| Pattern                      | Good for                                                            |\n| ---------------------------- | ------------------------------------------------------------------- |\n| **Follower reads**           | Low-latency reads that tolerate a small staleness window            |\n| **Geo-partitioned replicas** | Tables with a clear regional home (e.g. user rows pinned by region) |\n| **Follow-the-workload**      | Apps where the \"active\" region shifts throughout the day            |\n\nFor a typical web app, follower reads are the cheapest win. One clause and most of your read path stops crossing oceans:\n\n```sql\nSELECT * FROM users\n  AS OF SYSTEM TIME follower_read_timestamp()\n  WHERE id = $1;\n```\n\nWrites still cross regions for consensus, but the reads — usually the bulk of traffic — come from the same box your user is already talking to.\n\n## Who does what\n\n| Layer             | Tool                                   | Runs where                     |\n| ----------------- | -------------------------------------- | ------------------------------ |\n| DNS + geo-routing | Cloudflare                             | Cloudflare edge                |\n| TLS, proxy, app   | [Tako](/docs/how-tako-works)           | Each VPS                       |\n| Application       | Your code + the [`tako.sh` SDK](/docs) | Each VPS, managed by Tako      |\n| Data              | CockroachDB                            | Each VPS, as a systemd service |\n\nNothing you don't own. Nothing you can't `ssh` into.\n\n## The point\n\nTako is an app platform, not a database — we're happy with that split. Our job is to make the stuff around your code (routing, TLS, secrets, rolling updates, [local dev with real HTTPS](/blog/local-dev-with-real-https)) disappear, so you can pair it with whatever you want underneath. CockroachDB is a great fit because its \"drop a binary on every box\" model matches Tako's.\n\nThree regions, two binaries per box, one config file, one passphrase-derived key for the secrets. The whole stack sitting on hardware you pay by the month for. No Kubernetes, no managed database bill, no control plane to babysit — just processes and data, where you want them.\n\n[Get started →](/docs)"}