Back to Release Notes

Tako: Write Go. Get a Cloud.

Invisible infrastructure over Google Cloud Run.

Back when I wrote Serving Totopos, getting a small web server onto Cloud Run meant walking through a laundry list of gcloud commands: enabling APIs one by one, minting service accounts, wiring up IAM bindings by hand, setting up Cloud Build, and keeping all of that in sync whenever the code changed.

And that was just for one service reading from one bucket. Add a database, a background queue, a cron job, a couple of secrets, and a paging alert, and suddenly you are maintaining your application twice: once in Go, and once across Dockerfiles, Terraform, and YAML.

I’ve always loved language tooling and runtime plumbing — the concurrency, the performance, the nitty-gritty details that make a project challenging. But as a developer, you shouldn’t have to fight the machinery just to get an idea into the cloud. Your Go code already expresses what it needs. You shouldn’t have to repeat your compute footprint, IAM permissions, or wiring in YAML.

I built Tako to shrink that surface area: declare the intent in Go, run the entire graph locally in one process, and let the tool derive the cloud.

Declaring What Exists

In Tako, a package is a service. You declare what it owns at the top of the file as ordinary Go variables:

package app

import "github.com/gsandoval/tako"

var (
    auth   = tako.Auth(tako.AuthConfig{Google: true})
    visits = tako.Entity[Visit]("visits")
    clicks = tako.Queue[Click]("clicks")

    Greet   = tako.API("GET /greet/{name}", greet)
    Cleanup = tako.Cron("every 24h", cleanup, tako.CronConfig{Singleflight: true})
)

Before anything builds, Tako’s static analyzer walks the AST:

  • IAM permissions follow the code: If a handler calls .Put() on an entity or .Publish() on a queue, Tako grants only those roles. If you delete the code, the next deploy revokes the grant.
  • Indexes build themselves: It reads your .Where() and .Order() calls and generates the Datastore composite indexes automatically.
  • Operations are compiler-checked: Cron schedules are validated at compile time, and alerts reference your actual Go variables (tako.CronMissed(Cleanup)). You can’t rename a worker function and silently leave a broken alert behind.
  • Zero Docker friction: tako deploy builds and packages digest-pinned distroless container images directly from your binary without needing a local Docker daemon running.

One Process Locally

The part that usually breaks my flow in cloud projects is the local setup: spinning up Docker Compose, waiting on Java emulators for Datastore and Pub/Sub, or mocking out auth.

tako dev runs the entire application graph inside a single native Go process:

  • Entities live in a single .tako/data/entity.db (bbolt) file. Want a clean slate? Delete .tako/data.
  • Queues and pub/sub topics are in-process Go channels calling your functions. If you put a breakpoint in an HTTP handler, you can step straight across the queue publish and land inside the worker.
  • tako.Auth injects a local dev bar in the browser so you can switch between test personas (alice, bob) in one click without touching GCP.
  • tako.NewTestApp(t) boots the same in-process stack inside go test, complete with a fake clock (app.AdvanceClock) and seeded chaos mode to test duplicate and out-of-order queue deliveries.

Eating My Own Cooking

This site (memo.engineer) now runs on Tako — cut over from Firebase (and before that, Totopo) to a single native Tako service on Cloud Run. Using tako.Site, the static Astro build is embedded into the same Go binary that serves the visit telemetry (POST /api/visit) and newsletter broadcasts (POST /api/subscribe, POST /api/broadcast), backed by Datastore entities and Google Sign-In. One binary, one origin, zero YAML.

If you want to take it for a spin:

go install github.com/gsandoval/tako/cmd/tako@latest

The full tutorial and API reference are over at tako.memo.engineer. I’m having fun building with it — hope it makes building on Cloud Run a little more pleasant for you too.