{"slug":"zero-downtime-deploys-without-a-container-in-sight","url":"https://tako.sh/blog/zero-downtime-deploys-without-a-container-in-sight/","canonical":"https://tako.sh/blog/zero-downtime-deploys-without-a-container-in-sight/","title":"Zero-Downtime Deploys Without a Container in Sight","date":"2026-04-07T04:43","description":"How Tako rolls out new versions with connection draining, health-checked readiness, and automatic rollback — all with native processes.","author":null,"image":"b724049be8bd","imageAlt":null,"headings":[{"depth":2,"slug":"the-rolling-update-sequence","text":"The rolling update sequence"},{"depth":2,"slug":"how-readiness-actually-works","text":"How readiness actually works"},{"depth":2,"slug":"connection-draining","text":"Connection draining"},{"depth":2,"slug":"the-protocol-under-the-hood","text":"The protocol under the hood"},{"depth":2,"slug":"no-containers-required","text":"No containers required"}],"markdown":"Zero-downtime deploys usually mean containers. Kubernetes rolling updates, Docker Swarm service convergence, Fly.io machine replacement. The assumption is that you need an abstraction layer — something that can spin up a fresh container, health-check it, and tear down the old one.\n\nTako does all of that with native processes. No Docker, no container runtime, no image registry. Just your app, a [Pingora-based proxy](/blog/pingora-vs-caddy-vs-traefik), and a unix socket protocol that orchestrates the whole thing.\n\n## The rolling update sequence\n\nWhen you run [`tako deploy`](/docs/deployment), the CLI builds your app locally, uploads the artifact via SFTP, and sends a `Deploy` command over the server's unix socket. What happens next is a one-at-a-time rolling update:\n\n```d2\ndirection: down\n\nstart: Deploy command received {style.fill: \"#9BC4B6\"; style.font-size: 18}\nspawn: Spawn new instance {style.fill: \"#9BC4B6\"; style.font-size: 18}\nready: Wait for TAKO:READY {style.fill: \"#FFF9F4\"; style.stroke: \"#2F2A44\"; style.font-size: 18}\nhealth: Health check passes {style.fill: \"#9BC4B6\"; style.font-size: 18}\ndrain: Drain old instance {style.fill: \"#E88783\"; style.font-size: 18}\nwait: Wait for in-flight requests {style.fill: \"#E88783\"; style.font-size: 18}\nstop: Stop old process {style.fill: \"#E88783\"; style.font-size: 18}\ndone: Repeat for next instance {style.fill: \"#9BC4B6\"; style.font-size: 18}\n\nstart -> spawn: batch size: 1\nspawn -> ready: \"stdout: TAKO:READY:12345\"\nready -> health: probe /status\nhealth -> drain: mark old as Draining\ndrain -> wait: \"max 30s\"\nwait -> stop: kill process\nstop -> done\n```\n\nThe key detail: the old instance isn't touched until the new one is verified healthy. If the new instance fails to start or its health check times out (30 seconds), Tako kills the new instance and keeps the old ones running. Automatic rollback, no intervention needed.\n\n## How readiness actually works\n\nMost deploy tools check health by poking a TCP port. If the socket accepts connections, the app must be ready. But that's a guess — your server might be listening while still loading config or running migrations.\n\nTako uses an explicit readiness signal. The [SDK](/docs) handles this automatically:\n\n1. Your app starts, runs any initialization (DB connections, cache warming)\n2. The SDK binds to an OS-assigned port (`PORT=0`)\n3. It writes `TAKO:READY:12345` to stdout\n4. The server picks up the port and begins health probing\n\nYour app can also define a `ready()` hook for custom initialization logic — the SDK won't signal readiness until it completes. This means traffic only reaches instances that are genuinely ready to serve.\n\nOnce ready, the server probes every 1 second with a request to the SDK's built-in `/status` endpoint (using the internal `Host: tako` header). One failed probe marks the instance unhealthy and pulls it from the load balancer.\n\n## Connection draining\n\nThis is where zero-downtime actually happens. When an old instance enters the `Draining` state:\n\n1. The [load balancer](/docs/how-tako-works) stops sending it new requests\n2. In-flight requests continue to completion (up to 30 seconds)\n3. Once the in-flight counter hits zero, the process is killed\n\nNo request is ever dropped mid-response. The proxy tracks active connections per instance, and draining is a hard guarantee — not a best-effort grace period.\n\n| Phase             | Timeout     | What happens on timeout                        |\n| ----------------- | ----------- | ---------------------------------------------- |\n| Startup readiness | 30s         | New instance killed, old instances kept        |\n| Health check      | 1s interval | 1 failure → unhealthy, removed from rotation   |\n| Connection drain  | 30s         | Process killed (in-flight requests terminated) |\n\n## The protocol under the hood\n\nThe CLI and server communicate over a unix socket at `/var/run/tako/tako.sock` using newline-delimited JSON. A deploy sends two commands:\n\n**`PrepareRelease`** — extracts the artifact, downloads the runtime (Bun or Node), and runs `npm ci` / `bun install`. This happens _before_ any instance swap, so dependency installation doesn't eat into your downtime window.\n\n**`Deploy`** — carries the app name, version, release path, routes, and (optionally) secrets. This triggers the rolling update. Secrets are delivered to each new instance via file descriptor 3 — they never touch disk or environment variables. If the secrets hash hasn't changed since the last deploy, they're [skipped entirely](/blog/secrets-without-env-files).\n\nThe two-phase design keeps instance startup fast. By the time `Deploy` fires, everything is already installed. The new process just needs to boot your app and signal readiness.\n\n## No containers required\n\nThe entire flow — spawn, health-check, drain, kill — is the same pattern that Kubernetes uses for rolling deployments. The difference is that Tako does it with native processes managed by a single Rust binary, proxied through Pingora.\n\nNo Docker daemon. No image layers. No container networking. Just processes, a proxy, and a protocol.\n\nCheck out the [deployment guide](/docs/deployment) for the full setup, or [how Tako works](/docs/how-tako-works) for the architecture behind it."}