Guide

From Vercel or Netlify to a Container: The Moment You Need a Real Server

SnapDeploy Team 2026-11-16 Updated 2026-11-16 8 min read
vercelnetlifymigrationnodejsfree-tier

Vercel and Netlify are excellent at the thing they were built for: a front end, a CDN, and functions that answer a request and go away. Most projects never need more. This guide is for the moment yours does: the WebSocket that has nowhere to live, the job that takes two minutes, the queue worker, the "non-commercial use only" line you just re-read in the terms. It covers what the free tiers actually allow, the shapes of app that outgrow them, and the move, which is usually smaller than people fear because the front end can stay exactly where it is.

The free tiers, precisely

Vercel Hobby Netlify Free
Who may use itPersonal, non-commercial projects only. Anything that earns money for anyone involved needs Pro ($20 per member per month).No such clause.
Traffic100 GB fast data transfer a month; no overage, resources pause at the cap300 credits a month; bandwidth costs 20 credits per GB, so about 15 GB if spent on traffic
Functions1 million invocations, 4 hours of active CPU, 360 GB-hours of memory a month; up to 60 seconds per invocation125,000 invocations a month; short synchronous timeouts (10 seconds on the lower plans); background functions are a paid feature
Long-lived connectionsNo (functions end)No
What stays greatPreviews per pull request, edge caching, image optimization, custom domainsDeploy previews, forms, redirects, custom domains

The four shapes that outgrow a function

  • A connection that stays open. WebSockets, server-sent events that run for minutes, a game lobby, a live dashboard. A function is a request; a server is a process.
  • Work that takes longer than the timeout. Report generation, a scrape, a batch import, an LLM call chain. You can chain functions and queues to fake it; a container just runs the loop.
  • A worker. Anything that consumes a queue or polls something on a schedule that is not "when a request arrives".
  • The commercial clause. A side project that charges three customers is commercial on Vercel's Hobby plan. On a container free tier that question is not asked.

What a container does not fix: it does not give you a global edge for free, it sleeps after 15 minutes without traffic on the free tier, and previews per branch are manual. Those are the reasons to keep the front end where it is.

The move that usually works: split, don't migrate

Leave the static front end on Vercel or Netlify. Move the part that needs a server into a container, give it an API URL, and point the front end at it. The front end keeps its CDN and previews; the server gets a process. Three things to set up:

  1. The server. Take the function handlers and mount them on a small server (Express, Hono, FastAPI, whatever the functions were written in). A Vercel or Netlify function is a request handler already; the wrapper changes, the body does not.
  2. CORS. The front end now calls another origin. Allow exactly that origin, and read it from an environment variable so previews and production can differ.
  3. Secrets. Move server-side environment variables to the container. Anything prefixed for the browser (NEXT_PUBLIC_, VITE_) stays with the front end.
// api/server.js: the same handlers, now a process
const express = require("express");
const app = express();
app.use(express.json());
app.use((req, res, next) => {
  res.set("Access-Control-Allow-Origin", process.env.FRONTEND_ORIGIN);
  res.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
  if (req.method === "OPTIONS") return res.sendStatus(204);
  next();
});
app.get("/health", (req, res) => res.send("ok"));
app.post("/api/report", require("./handlers/report"));   // the old function body
app.listen(process.env.PORT || 3000, "0.0.0.0");
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "api/server.js"]

If the server is a full Next.js app rather than an API, the standalone-output Dockerfile in our Next.js guide is the right shape, and the same CORS point does not apply because pages and API share an origin again.

Long jobs and workers on a container that sleeps

A two-minute request is fine: the container is awake while it runs, and nothing times it out. A worker that must run when nobody is visiting is the honest limit of the free tier: a container with no HTTP traffic is idle by definition and sleeps after 15 minutes. Options, in order: do the work in the request that needs it; trigger it from the front end's scheduled function (Vercel cron or Netlify scheduled functions, which are free and can call your container's endpoint on a schedule, waking it if needed); or make the worker container Always-On ($12/month), where it simply keeps running.

Deploying the server

  1. Push the repository (or a new repository for the server). Containers → Deploy from GitHub → pick it; port 3000.
  2. Set FRONTEND_ORIGIN and the server-side secrets. Attach a database add-on if the functions used one, or paste the connection string of the one they already use.
  3. Deploy; test /health on the containers.snapdeploy.app URL, then point the front end's API base URL at it and redeploy the front end.
  4. Keep the old functions for a week, then delete them so the secrets do not live in two places.

Where free stops on the container side

  • 4 containers, 512 MB and 0.25 vCPU each, 100 container-hours a month counted while awake. An API called from a busy front end stays awake and spends hours; one called a few times an hour sleeps between calls.
  • The first request after 15 idle minutes waits about a minute. For an API behind a front end that is a slow first fetch, not a wake page; show a loading state.
  • Custom domains only on Always-On. Most split setups do not need one on the API.

Test the server locally first

docker build -t api .
docker run --rm -p 3000:3000 -e PORT=3000 -e FRONTEND_ORIGIN=http://localhost:5173 --env-file .env.local api
curl -i -H "Origin: http://localhost:5173" http://localhost:3000/health

The response should carry the Access-Control-Allow-Origin header; if it does not, the browser will refuse the real calls and the symptom looks like a network error rather than a CORS one.

Numbers used: Vercel Hobby limits and fair-use terms and Netlify's Free plan from their pricing and documentation pages as of September 2026; SnapDeploy free tier from the free tier docs.

Ready to Deploy?

Deploy free. 10 deploys a day, 100 hours a month, no credit card.

Run this yourself: Get a dedicated NVIDIA T4 (16 GB) for a flat $499/mo or A10G (24 GB) for $999/mo — never shared, never spot, no hourly metering. See dedicated GPU hosting →

One-click Ollama, vLLM, ComfyUI, Whisper, PyTorch & more — or deploy your own GitHub repo or Docker image. Compare plans.

FIRST OF ITS KIND

Sprint Pack — ₹99 for 24 hours

The first managed container PaaS to offer a one-time 24-hour Always-On pass under $5. Unlimited deploys + Always-On for one Small (512 MB) container — including WebSockets & real-time apps. No subscription, no auto-renewal. Stack two for 48 hours.

Need a managed database too? DB Sprint Pack — ₹99 / 12h for Postgres, MySQL, MariaDB, Mongo, Redis, or RabbitMQ.

Tip: Need 24 hours of Always-On for a demo, weekend, or quick test — or WebSockets & real-time apps? Sprint Pack is ₹99 one-time — no subscription, no auto-renewal.

Need a managed Postgres / MySQL / Mongo / Redis instead? DB Sprint Pack — ₹99 / 12h.

Take SnapDeploy with you

Deploy, monitor and wake your containers from your iPhone.

Download on the App Store

Get DevOps Tips & Updates

Container deployment guides, platform updates, and DevOps best practices. No spam.

Unsubscribe anytime. We respect your privacy.

More Articles