{"slug":"how-to-deploy-nextjs-to-a-vps-without-docker","url":"https://tako.sh/blog/how-to-deploy-nextjs-to-a-vps-without-docker/","canonical":"https://tako.sh/blog/how-to-deploy-nextjs-to-a-vps-without-docker/","title":"How to Deploy Next.js to a VPS Without Docker","date":"2026-04-29T13:00","description":"A literal walkthrough — provision a $5 VPS, point a domain, and ship a Next.js app to it with tako init + tako deploy. HTTPS and zero-downtime rollouts, no Dockerfile in sight.","author":null,"image":"596d90391f86","imageAlt":null,"headings":[{"depth":2,"slug":"what-you-need","text":"What you need"},{"depth":2,"slug":"step-1--install-the-cli-and-tako-server","text":"Step 1 — Install the CLI and tako-server"},{"depth":2,"slug":"step-2--wire-up-nextjs","text":"Step 2 — Wire up Next.js"},{"depth":2,"slug":"step-3--tako-init","text":"Step 3 — tako init"},{"depth":2,"slug":"step-4--tako-deploy","text":"Step 4 — tako deploy"},{"depth":2,"slug":"what-just-happened","text":"What just happened"},{"depth":2,"slug":"what-you-didnt-write","text":"What you didn’t write"}],"markdown":"The two paths most Next.js deploy tutorials show: push to Vercel, or write a Dockerfile and ship the image somewhere. The first is the easiest thing in the world — until you want to actually own the box. The second works, but you've signed up for Dockerfiles, multi-stage builds, image registries, and a `docker-compose.yml` to run a process that fundamentally just needs Node and a port.\n\nThere's a third path. A $5 VPS, a domain, and [Tako](/docs). No container in sight.\n\n## What you need\n\nFive things. None of them include Docker.\n\n| Thing                         | Where it comes from                                                                                                    |\n| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------- |\n| A VPS                         | [Hetzner CX22 (~$6/mo)](/blog/your-5-dollar-vps-is-more-powerful-than-you-think), DigitalOcean, Vultr — anything Linux |\n| A domain                      | Wherever; point an A record at the VPS IP                                                                              |\n| `tako-server` on the box      | One curl command                                                                                                       |\n| The `tako` CLI on your laptop | One curl command                                                                                                       |\n| A Next.js app                 | `npx create-next-app@latest my-app`                                                                                    |\n\n## Step 1 — Install the CLI and `tako-server`\n\nOn your laptop:\n\n```bash\ncurl -fsSL https://tako.sh/install.sh | sh\n```\n\nSSH into the VPS and run the server installer:\n\n```bash\nsudo sh -c \"$(curl -fsSL https://tako.sh/install-server.sh)\"\n```\n\nThat's the entire server-side setup. The installer drops a single Rust binary, registers a systemd unit, creates a non-root `tako` user, and grants it the capability to bind ports 80 and 443. Pingora proxy, ACME, process supervision, and the encrypted secrets store all live inside that one binary. The [deployment docs](/docs/deployment) cover the details, but defaults work — skip them on the first pass.\n\n## Step 2 — Wire up Next.js\n\nThere is exactly one line of Next-specific config. In your `next.config.ts`:\n\n```ts\nimport { withTako } from \"tako.sh/nextjs\";\n\nexport default withTako({});\n```\n\n`withTako()` switches Next.js to standalone output, installs the Tako adapter, and lets `next dev` accept Tako's local HTTPS hostnames (`*.test`). On build it emits `.next/tako-entry.mjs` — the small file Tako launches in production. The [framework guide](/docs/framework-guides#nextjs) has the full breakdown.\n\n## Step 3 — `tako init`\n\nIn the project directory:\n\n```bash\ntako init\n```\n\nInit reads your `package.json`, sees `next`, and offers the `nextjs` preset. Accept the defaults and you'll get a `tako.toml` like this:\n\n```toml\nname = \"my-app\"\nruntime = \"node\"\nruntime_version = \"22.x\"\npreset = \"nextjs\"\n\n[envs.production]\nroute = \"my-app.example.com\"\nservers = [\"prod\"]\n```\n\nInit also runs `npm add tako.sh` (or `bun add`, depending on your package manager) to drop the SDK in, and tunes `.gitignore` so `.tako/*` is ignored — except `.tako/secrets.json`, which stays tracked. The [`nextjs` preset](/docs/presets) bakes in `main = \".next/tako-entry.mjs\"` so you don't write that line yourself.\n\nChange `route` to a domain you actually own, then register the server once:\n\n```bash\ntako servers add prod.example.com --name prod\n```\n\n## Step 4 — `tako deploy`\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 Next.js app is live, on a real Let's Encrypt cert, with [zero-downtime rolling updates](/blog/scale-to-zero-without-containers) on every subsequent `tako deploy`.\n\n## What just happened\n\n```d2\ndirection: right\n\nlocal: Your laptop {\n  build: \"next 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  node: \"Node process\\n(.next standalone)\" {\n    style.fill: \"#9BC4B6\"\n  }\n  proxy -> node: \"fetch()\"\n}\n\nlocal.build -> artifact: \"package\"\nartifact -> server: \"SFTP\"\n```\n\n`tako deploy` ran `next build` locally, packaged the result (excluding `.git`, `.tako`, `.env*`, and `node_modules`) into a `.tar.zst` artifact, and SFTP'd it to the VPS. `tako-server` unpacked it under `/opt/tako/apps/my-app/production/`, ran a production install, and started the Next.js standalone server as a regular Node process. [Pingora](/blog/pingora-vs-caddy-vs-traefik) terminates TLS on `:443` and routes to it.\n\n## What you didn't write\n\n- A `Dockerfile`\n- A `docker-compose.yml`\n- An `nginx.conf` and a `certbot` cron\n- A `.env` file copied into a container at build time\n- A GitHub Actions workflow that builds, tags, and pushes an image\n\nThat's the difference. Next.js doesn't need to be containerized to run; it's a Node program that listens on a port. Tako treats it like one. The proxy, the TLS, the secrets store, and the rolling-restart coordinator all live in a single Rust binary on the box. Your Next.js app is a process, not a container image.\n\nWhen you're ready for more — [secrets that don't sit in env files](/blog/secrets-without-env-files), [multiple environments on the same box](/blog/one-config-many-servers), or [durable workflows from inside your routes](/blog/tako-workflows-in-nextjs-via-instrumentation) — it's all the same `tako deploy`. Start with the [CLI reference](/docs/cli) or skim [how Tako works](/docs/how-tako-works)."}