Comparison

Next.js on a Container Versus Vercel: When Server Rendering Needs a Server

SnapDeploy Team 2026-10-23 Updated 2026-10-23 8 min read
nextjsvercelreactdockerfree-tier

Vercel is where Next.js is designed to run, and for a personal project its free Hobby plan is hard to argue with. This is not an argument against it. It is a map of the line where a Next.js app stops fitting a serverless free tier and starts fitting a container, with the real limits on both sides and a Dockerfile that produces a small image.

What Vercel's Hobby plan actually gives you

  • Free, with one condition that matters: personal, non-commercial use only. A side project that takes payments, shows ads or belongs to a company is outside the terms; the next plan is Pro at $20 per member per month.
  • 100 GB of fast data transfer, 1 million function invocations, 4 hours of active CPU and 360 GB-hours of provisioned memory a month.
  • Functions run up to 60 seconds. No overage billing on Hobby: when a limit is hit, the resource is unavailable until the next cycle.
  • Preview deployments per pull request, the global CDN, image optimization and Incremental Static Regeneration, all wired in without configuration.

If your app is a static or mostly static site, a portfolio, docs, or a blog, and it is non-commercial, stop reading and use Vercel. Nothing in a container tier beats that deal for that shape.

Where a container fits better

  • Commercial hobby projects. A small SaaS with three paying users is "commercial". On a container free tier the question is not asked; the trade is that the container sleeps after 15 minutes without traffic and takes about a minute to wake.
  • A real server. WebSockets, long-lived connections, an in-process job queue, a custom server.js, or a request that legitimately takes longer than a minute. Serverless functions end; a container process does not.
  • No per-invocation ceiling. A container has no invocation, CPU-second or memory-hour budgets, only the box it runs in: 512 MB and 0.25 vCPU on the free tier, and 100 container-hours a month.
  • Everything in one place. A Next.js front end next to a Python API and a managed Postgres, in the same dashboard, with environment variables you set once.

The Dockerfile: standalone output

Next.js can emit a self-contained server with only the files it needs. Turn it on in next.config.js:

/** @type {import('next').NextConfig} */
const nextConfig = { output: 'standalone' };
module.exports = nextConfig;
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production PORT=3000 HOSTNAME=0.0.0.0
RUN addgroup -S app && adduser -S app -G app
COPY --from=build --chown=app:app /app/.next/standalone ./
COPY --from=build --chown=app:app /app/.next/static ./.next/static
COPY --from=build --chown=app:app /app/public ./public
USER app
EXPOSE 3000
CMD ["node", "server.js"]

The standalone folder contains server.js and a pruned node_modules, so the final image is the Node runtime plus what the app really uses, typically well under 200 MB rather than the 500 MB+ you get by copying the whole project. HOSTNAME=0.0.0.0 matters: the standalone server binds to localhost by default, which the platform cannot reach.

Why your own Dockerfile rather than the generated one: SnapDeploy's generated Node.js Dockerfile builds on a Node 18 base image. Next.js 15 still runs there, but recent releases require Node 20 or newer, and the standalone layout above is not something a generic generator knows about. With a Dockerfile in the repository, SnapDeploy uses it and reads the port from EXPOSE. If your pages use next/image, install sharp as a dependency; it is compiled during npm ci on the Alpine image without extra steps.

What changes without Vercel's platform features

Feature On Vercel In a container
Static assets and CDNGlobal edge cache automaticallyServed by the Node process; put a CDN in front if traffic is global. On SnapDeploy every container URL is already behind Cloudflare.
Incremental Static RegenerationShared cache across functionsWorks; the cache lives in .next/cache inside the container and is rebuilt after a restart or a wake.
Image optimizationBuilt inDone by the server with sharp; CPU-bound on a quarter vCPU, so pre-size images or use an external loader.
Preview per pull requestAutomaticManual: deploy a branch to a second container (the free tier has four).
Cold startFunction cold starts, usually well under a secondNone while awake; about 60 seconds to wake a free container that slept, none on Always-On ($12/month).

Memory and CPU, measured

A standalone Next.js server with the App Router idles around 100 to 150 MB and stays under 300 MB serving a typical page mix, so 512 MB works. The constraint is CPU: server rendering a heavy page on 0.25 vCPU takes several times longer than on your laptop. Static generation at build time (generateStaticParams, ISR with a long revalidate) moves that work to the build, where it belongs, and the container mostly serves files.

Deploying

  1. Add output: 'standalone' and the Dockerfile; commit a .dockerignore with node_modules, .next and .env*.
  2. Push. Containers → Deploy from GitHub → pick the repository. Port 3000.
  3. Add environment variables. NEXT_PUBLIC_* values are baked in at build time, so set them before the first build.
  4. Deploy. Builds take a few minutes (the npm run build step dominates). Test the container URL, then the same page after 20 idle minutes to see the wake page once.

Check the image before you push

docker build -t site .
docker images site --format "{{.Size}}"
docker run --rm -p 3000:3000 site
curl -I http://localhost:3000/

If the size is several hundred megabytes, the standalone folder was not used (check next.config.js) or node_modules is being copied in. If the page renders but images are broken, public/ or .next/static was not copied into the runtime stage. If the container starts but nothing answers on port 3000, HOSTNAME is missing and the server is bound to localhost.

Environment variables: two kinds

Next.js treats NEXT_PUBLIC_* variables as build-time constants: they are inlined into the client bundle when npm run build runs, so they must be present during the build, and changing one means a rebuild. Everything else (database URLs, API keys, secrets) is read at runtime by server components, route handlers and middleware, and can be changed in the container settings followed by a restart. A common mistake is putting a secret in a NEXT_PUBLIC_ name; it ends up in the JavaScript you ship to every browser.

Where free stops

  • The container sleeps after 15 minutes without traffic; the first visitor afterwards waits about a minute on a wake page. Vercel has no equivalent wait.
  • 100 container-hours a month across four containers, counted while awake. A site visited a few times a day is fine; one that is pinged to stay up is not.
  • Custom domains need Always-On ($12/month). Vercel's Hobby plan includes them.
  • No built-in analytics, speed insights or per-branch previews; the free tier's four containers are what you have for previews.

Numbers used: Vercel Hobby limits from Vercel's plan and limits pages as of September 2026; SnapDeploy free tier from the free tier docs (4 containers, 512 MB, 0.25 vCPU, 100 container-hours a month, sleeps after 15 minutes, no card).

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