{"slug":"deploy-tanstack-start-to-a-vps-in-five-minutes","url":"https://tako.sh/blog/deploy-tanstack-start-to-a-vps-in-five-minutes/","canonical":"https://tako.sh/blog/deploy-tanstack-start-to-a-vps-in-five-minutes/","title":"Deploy TanStack Start to a VPS in Five Minutes","date":"2026-04-29T12:44","description":"A concrete walkthrough: scaffold a TanStack Start app, run tako init and tako deploy, and watch the SSR bundle boot natively on Bun behind Pingora — no Docker, no edge platform.","author":null,"image":"fd31b5e47c64","imageAlt":null,"headings":[{"depth":2,"slug":"step-1--scaffold-the-app","text":"Step 1 — Scaffold the app"},{"depth":2,"slug":"step-2--tako-init","text":"Step 2 — tako init"},{"depth":2,"slug":"step-3--tako-deploy","text":"Step 3 — tako deploy"},{"depth":2,"slug":"what-just-happened","text":"What just happened"},{"depth":2,"slug":"why-this-matters","text":"Why this matters"}],"markdown":"[TanStack Start](https://tanstack.com/start/latest) is a full-stack React framework with file-based routing, server functions, and a real SSR build. Most tutorials for it end with \"now deploy to Netlify / Vercel / Cloudflare Workers.\" Those work, but they're not your only option. The SSR output is a Node-compatible fetch handler — which means a single Linux box with [Tako](/docs) on it is enough.\n\nHere's the whole walkthrough. Total wall-clock time, assuming a server is already provisioned: about five minutes.\n\n## Step 1 — Scaffold the app\n\n```bash\nnpx @tanstack/cli@latest create my-app\ncd my-app\n```\n\nPick Bun as the package manager when the wizard asks (Node works too — Tako supports both). Add Tailwind or whatever else you want. The default starter is a working SSR app right out of the box.\n\nAdd the [Tako Vite plugin](/docs/framework-guides) so the build emits a server entry Tako can launch:\n\n```ts\n// vite.config.ts\nimport { defineConfig } from \"vite\";\nimport { tanstackStart } from \"@tanstack/react-start/plugin/vite\";\nimport { tako } from \"tako.sh/vite\";\n\nexport default defineConfig({\n  plugins: [tanstackStart(), tako()],\n});\n```\n\nThat's the only framework-side change. The plugin doesn't replace TanStack Start's build — it adds a thin wrapper at `dist/server/tako-entry.mjs` that re-exports the SSR fetch handler in [the shape Tako expects](/blog/the-fetch-handler-pattern).\n\n## Step 2 — `tako init`\n\n```bash\ntako init\n```\n\nInit is interactive. It detects Bun from your lockfile, sees `@tanstack/react-start` in `package.json`, and offers `tanstack-start` as the preset. Accept the defaults and you'll get a `tako.toml` like this:\n\n```toml\nname = \"my-app\"\nruntime = \"bun\"\nruntime_version = \"1.2.x\"\npreset = \"tanstack-start\"\n\n[envs.production]\nroute = \"my-app.example.com\"\nservers = [\"prod\"]\n```\n\nThe [`tanstack-start` preset](/docs/presets) bakes in `main = \"dist/server/tako-entry.mjs\"` and `assets = [\"dist/client\"]`, so you don't write either. Init also runs `bun add tako.sh` so the SDK is in your dependencies, and updates `.gitignore` so `.tako/*` is ignored while `.tako/secrets.json` stays tracked.\n\nChange `route` to a domain you actually own. Tako will issue a Let's Encrypt cert for it on first deploy.\n\n## Step 3 — `tako deploy`\n\nIf you don't have a server registered yet, do that once:\n\n```bash\ntako servers add prod.example.com --name prod\n```\n\nThis connects as the `tako` user, detects `arch` and `libc`, and writes the entry to your global `config.toml`. ([How to install `tako-server` on the box](/docs/deployment) is in the deploy docs — `apt install tako-server` on Debian / Ubuntu, equivalent on Alpine.)\n\nThen:\n\n```bash\ntako deploy\n```\n\nConfirm the production prompt and watch the task tree:\n\n```\nConnecting     ✓\nBuilding       ✓\nDeploying to prod\n  Uploading    ✓\n  Preparing    ✓\n  Starting     ✓\n\n  https://my-app.example.com/\n```\n\nOpen the URL. Your TanStack Start app is live, with a real cert, behind [Pingora](/blog/pingora-vs-caddy-vs-traefik), running natively on Bun.\n\n## What just happened\n\n```d2\ndirection: right\n\nlocal: Your laptop {\n  build: \"vite build\\n+ tako-entry.mjs\"\n}\n\nartifact: \".tar.zst\\nartifact\" {\n  style.fill: \"#FFF9F4\"\n  style.stroke: \"#2F2A44\"\n}\n\nserver: VPS {\n  proxy: \"Pingora\\n(:443, TLS)\" {\n    style.fill: \"#E88783\"\n  }\n  bun: \"Bun process\\n(SSR handler)\" {\n    style.fill: \"#9BC4B6\"\n  }\n  proxy -> bun: \"fetch()\"\n}\n\nlocal.build -> artifact: \"package\"\nartifact -> server: \"SFTP\"\n```\n\nThe deploy ran [`vite build`](/docs/deployment) locally, packaged everything (excluding `.git`, `.tako`, `.env*`, and `node_modules`) into a `.tar.zst` artifact, and SFTP'd it to your server. `tako-server` unpacked it to `/opt/tako/my-app/releases/{version}/`, ran a production install, and started the SSR bundle directly under Bun. Pingora terminates TLS on `:443`, the per-app load balancer routes to your process, and the SDK answers Tako's internal health checks so unhealthy instances drop out automatically.\n\nThere is no container, no Node-on-Bun shim, no Lambda cold start. It's a Linux process started by a service manager on a VPS — but you got there with two commands.\n\n## Why this matters\n\nTanStack Start runs anywhere a fetch handler runs: Vercel, Netlify, Cloudflare Workers, Bun on a box. The hosted platforms are the loudest option in the room, and they're great at what they do — but they aren't the only option, and the lock-in tradeoff is real. With Tako you keep the same SSR app and the same fetch-handler interface, deployed to hardware you control, with [zero-downtime rolling updates](/blog/scale-to-zero-without-containers) and [HTTPS in local dev](/blog/local-dev-with-real-https) thrown in.\n\nFive minutes from `create-start-app` to live HTTPS. Try the rest of the [CLI reference](/docs/cli) when you want secrets, multi-server routing, or rollbacks."}