{"slug":"full-stack-nextjs-app-one-5-vps-https-jobs-websockets-images","url":"https://tako.sh/blog/full-stack-nextjs-app-one-5-vps-https-jobs-websockets-images/","canonical":"https://tako.sh/blog/full-stack-nextjs-app-one-5-vps-https-jobs-websockets-images/","title":"How to Run a Full-Stack Next.js App on One $5 VPS: HTTPS, Jobs, WebSockets, and Images","date":"2026-06-07T00:19","description":"Deploy a full-stack Next.js app to one cheap VPS with Tako: HTTPS, rolling deploys, workflows, WebSocket/SSE channels, and image optimization.","author":null,"image":"cbd3c4bcfbec","imageAlt":null,"headings":[{"depth":2,"slug":"start-with-the-nextjs-runtime","text":"Start with the Next.js runtime"},{"depth":2,"slug":"add-jobs-websockets-and-images","text":"Add jobs, WebSockets, and images"},{"depth":2,"slug":"deploy-the-whole-app","text":"Deploy the whole app"}],"markdown":"One cheap VPS can run more than a homepage.\n\nThe interesting question is what happens after the first deploy. A real Next.js app wants HTTPS, static assets, API routes, background jobs, WebSocket or SSE updates, image optimization, secrets, logs, and a deploy path that does not turn every change into a tiny incident. You can bolt those together from separate tools. Or you can make the VPS run like a small app platform.\n\nThis is the Tako version: one server, one Next.js app, one [`tako.toml`](/docs/tako-toml/), and the full-stack pieces most apps reach for after week two. The deeper reference pages are the [Next.js framework guide](/docs/framework-guides/#nextjs), [deployment docs](/docs/deployment/), [development guide](/docs/development/), and [CLI reference](/docs/cli/).\n\n## Start with the Next.js runtime\n\nNext.js is portable because it can run as a Node.js server. The current Next.js docs describe `output: \"standalone\"` as a deployment mode that writes a minimal `.next/standalone/server.js`; running that server starts the production app. Tako wraps that path instead of inventing a separate Next runtime.\n\nInstall the SDK and wrap your config:\n\n```bash\nbun add tako.sh\n```\n\n```ts\n// next.config.ts\nimport { withTako } from \"tako.sh/nextjs\";\n\nexport default withTako({\n  images: {\n    remotePatterns: [\n      {\n        protocol: \"https\",\n        hostname: \"cdn.example.com\",\n        pathname: \"/uploads/**\",\n      },\n    ],\n  },\n});\n```\n\n`withTako()` enables standalone output, installs the Tako adapter, adds `*.test` and `*.tako.test` to Next's allowed dev origins, writes `.next/tako-entry.mjs`, and configures `next/image` to use Tako's public optimizer. If Next emits standalone output, Tako uses it; otherwise the wrapper falls back to `next start`.\n\nThen keep the deployment config small:\n\n```toml\nruntime = \"bun\"\npreset = \"nextjs\"\napp_root = \".\"\n\n[envs.production]\nroutes = [\"app.example.com\"]\nservers = [\"vps-1\"]\n\n[images]\nremote_patterns = [\"https://cdn.example.com/uploads/**\"]\nformats = [\"webp\"]\n```\n\n`app_root = \".\"` is useful for root-level Next projects where `channels/`, `workflows/`, `instrumentation.ts`, and `tako.d.ts` live next to `next.config.ts`. If you keep backend definitions under `src/`, leave the default alone.\n\n| Piece                 | Where it lives                     | What Tako does with it                                        |\n| --------------------- | ---------------------------------- | ------------------------------------------------------------- |\n| Next server           | `.next/tako-entry.mjs`             | Starts the standalone server or `next start`                  |\n| Public/static assets  | `public/` and Next build output    | Serves matching files directly after route match              |\n| Image optimizer       | `/_tako/image`                     | Validates sources, transforms with libvips, caches variants   |\n| Durable channels      | `<app_root>/channels/*.ts`         | Serves WebSocket/SSE endpoints under `/_tako/channels/<name>` |\n| Durable workflows     | `<app_root>/workflows/*.ts`        | Runs jobs in supervised worker processes with persisted state |\n| Routes, TLS, rollouts | `[envs.production]` in `tako.toml` | Maps hostnames, manages certificates, and rolls new instances |\n\n## Add jobs, WebSockets, and images\n\nThe one file Next.js apps need for backend primitives is `instrumentation.ts`. Next standalone runs routes in a child process, so the Tako runtime needs to initialize there before a route or server action enqueues a workflow or publishes a channel message.\n\n```ts\n// instrumentation.ts\nexport async function register() {\n  if (process.env.NEXT_RUNTIME === \"nodejs\") {\n    const { initServerRuntime } = await import(\"tako.sh/internal\");\n    initServerRuntime();\n  }\n}\n```\n\nNow add a workflow:\n\n```ts\n// workflows/send-receipt.ts\nimport { defineWorkflow } from \"tako.sh\";\n\nexport default defineWorkflow<{ orderId: string; email: string }>(\"send-receipt\", {\n  retries: 4,\n  async handler(payload, ctx) {\n    await ctx.run(\"email\", async () => {\n      ctx.logger.info(\"sending receipt\", { orderId: payload.orderId });\n      // send the email here\n    });\n  },\n});\n```\n\nWorkflows give you named step checkpoints, retries, cron schedules, sleeps, signals, and workers that scale to zero by default. They ship with the app on [`tako deploy`](/docs/deployment/), read the same secrets, and run next to the HTTP process without a separate Redis queue.\n\nThen add a channel:\n\n```ts\n// channels/orders.ts\nimport { defineChannel } from \"tako.sh\";\n\nexport default defineChannel(\"orders\", {\n  auth: \"public\",\n}).$messageTypes<{\n  updated: { orderId: string; status: string };\n}>();\n```\n\nChannels are served at `/_tako/channels/orders`. The proxy owns the WebSocket/SSE connection, stores published messages in a bounded replay log, and lets reconnecting clients catch up from a retained cursor. Use your product database for canonical history; use channels for live delivery and short reconnect replay.\n\nYour Next route can now use both:\n\n```ts\n// app/api/orders/route.ts\nimport sendReceipt from \"@/workflows/send-receipt\";\nimport orders from \"@/channels/orders\";\n\nexport async function POST(req: Request) {\n  const order = await req.json();\n\n  await sendReceipt.enqueue({ orderId: order.id, email: order.email });\n  await orders().publish({\n    type: \"updated\",\n    data: { orderId: order.id, status: \"placed\" },\n  });\n\n  return Response.json({ ok: true });\n}\n```\n\nImages stay ordinary in React:\n\n```tsx\nimport Image from \"next/image\";\n\nexport function ProductHero() {\n  return <Image src=\"/images/product.jpg\" width={1200} height={800} alt=\"Product\" priority />;\n}\n```\n\nBecause `withTako()` configured the loader, the browser requests `/_tako/image?...` instead of sending image work through a custom app route. Local sources are allowed by default, remote sources must match `[images].remote_patterns`, and WebP is the default output format. The optimizer uses source and transform caches, so the first request for a size is the expensive one and repeated requests are cheap.\n\n```d2\ndirection: right\n\nbrowser: \"Browser\"\nproxy: \"tako-server\\nTLS + routes\"\nnext: \"Next.js\\npages + API routes\"\njobs: \"Workflow worker\"\nchannels: \"Channel replay\\nWebSocket/SSE\"\nimages: \"Image optimizer\\nlibvips + cache\"\n\nbrowser -> proxy: \"HTTPS\"\nproxy -> next: \"SSR / API\"\nnext -> jobs: \"enqueue\"\nnext -> channels: \"publish\"\nbrowser -> proxy: \"/_tako/channels/orders\"\nbrowser -> proxy: \"/_tako/image\"\nproxy -> channels: \"connect + replay\"\nproxy -> images: \"validate + transform\"\n```\n\n## Deploy the whole app\n\nInstall the server once:\n\n```bash\nsudo sh -c \"$(curl -fsSL https://tako.sh/install-server.sh)\"\ntako servers add prod-a.tailnet.ts.net --install\n```\n\nThen deploy:\n\n```bash\ntako generate\ntako deploy --env production\n```\n\nDeploy validates the production environment, routes, secrets, server metadata, image and channel/workflow requirements, then builds and packages the app. The server extracts the release under `/opt/tako/apps/{app}/{env}/releases/{version}/`, runs the runtime plugin's production install, starts a fresh instance, waits for health, adds it to the load balancer, and drains the old instance. Exact public routes use Let's Encrypt by default; wildcard routes can use Cloudflare DNS-01, and Cloudflare-proxied apps can use Cloudflare Origin CA.\n\nFor local development, run:\n\n```bash\ntako dev\n```\n\nYou get local HTTPS, `.test` hostnames, watched channel/workflow definitions, generated TypeScript declarations, local image optimizer routes, and the same shape as production. If something feels off, `tako doctor` checks the daemon, DNS, proxy, CA trust, and repair hints.\n\nThe point is not that one $5 VPS should run every company. The point is that a small box can be a complete application environment for a surprising amount of work. With Tako, the cheap server is not just a place where `next start` happens. It is the boundary that handles HTTPS, rollouts, jobs, WebSockets/SSE, images, logs, secrets, and routing for the app you already wrote.\n\nThat gives you a practical default shape:\n\n| App need                    | One-box answer                                       |\n| --------------------------- | ---------------------------------------------------- |\n| HTTPS and host routing      | Tako proxy, routes, certificates, redirects          |\n| API routes and SSR          | Next.js standalone server behind the proxy           |\n| Background work             | Durable workflow workers, idle when unused           |\n| Live updates                | Durable channels with bounded reconnect replay       |\n| Product and marketing media | `next/image` through Tako's optimizer and cache      |\n| Operational loop            | `tako logs`, `tako status`, `tako releases rollback` |\n\nWhen the app outgrows one machine, the same config model can add servers. Until then, the nice part is how little infrastructure vocabulary you need to introduce before shipping the full-stack version.\n\nStart with the [quickstart](/docs/quickstart/), skim the [Next.js guide](/docs/framework-guides/#nextjs), or inspect the [open-source repo](https://github.com/tako-sh/tako). The box is small. The app does not have to be."}