Skip to content
Docs

Deploy a Node.js Fastify app on Vercel with Docker

Build a Node.js application with Fastify and Docker, then deploy it to Vercel Functions. Learn how to configure environment variables, managed storage, and preview deployments for your project.

Anshuman BhardwajContent Engineer

Fastify is a low-overhead Node.js web framework for building APIs. Vercel already provides a native Node.js runtime, but a container is useful when your application depends on a specific Linux distribution, operating system packages, or an existing container build. Add a Dockerfile.vercel and set the service runtime to container. Vercel builds the image, stores it in the Vercel Container Registry, and serves it from a Function that autoscales with traffic and scales to zero when idle.

This guide deploys a minimal Fastify API, binds it to Vercel’s PORT, and explains how to use durable external storage instead of the container file system.

Copy link to headingPrerequisites

Copy link to headingHow it works

The project is one Fastify application with the following parts:

  • server.js creates the server and binds to 0.0.0.0 on $PORT.
  • Dockerfile.vercel installs production dependencies in one stage and runs the service in the final stage.
  • vercel.json declares a container service and rewrites incoming requests to it.
  • On deployment, Vercel builds the image and runs it as a Vercel Function.

Copy link to headingSteps

Copy link to heading1. Initialize the project

Create and initialize the project:

Terminal
mkdir vercel-docker-node
cd vercel-docker-node
npm init -y
npm install fastify

Set "type": "module" in package.json .

package.json
{
"name": "vercel-node-docker",
"private": true,
"type": "module",
"scripts": { "start": "node server.js" },
"dependencies": { "fastify": "^5.6.1" }
}

Now, for the HTTP server, create a new file called server.js:

server.js
import Fastify from "fastify";
const app = Fastify({ logger: true });
app.get("/", async () => ({ message: "Hello from Node.js on Vercel" }));
app.get("/health", async () => ({ status: "ok" }));
const port = Number.parseInt(process.env.PORT ?? "80", 10);
await app.listen({ host: "0.0.0.0", port });
process.on("SIGTERM", async () => {
const timer = setTimeout(() => process.exit(1), 25_000).unref();
await app.close();
clearTimeout(timer);
process.exit(0);
});

The server must bind to 0.0.0.0, not localhost, and read PORT. The fallback to port 80 matches Vercel's default container port.

Copy link to heading2. Add Dockerfile.vercel

Create the Dockerfile.vercel file in the project root:

Dockerfile.vercel
FROM node:24-alpine AS dependencies
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
FROM node:24-alpine
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY package.json server.js ./
ENV NODE_ENV=production PORT=80
USER node
CMD ["node", "server.js"]

Copying the manifests before the application code lets Docker reuse the dependency layer. The final stage contains Node.js and production dependencies, excludes npm's build cache, and runs as the image's non-root node user.

Copy link to heading3. Add vercel.json

Create a vercel.json file to configure your project:

vercel.json
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"services": {
"api": {
"root": ".",
"entrypoint": "Dockerfile.vercel",
"runtime": "container"
}
},
"rewrites": [
{ "source": "/(.*)", "destination": { "service": "api" } }
]
}

The service selects the container runtime and points to the Dockerfile. The catch-all rewrite sends every public path to the Fastify service.

Copy link to heading4. Run locally

Run the project through Vercel's local container environment:

Terminal
vercel dev -L

Use the URL printed by the CLI, usually http://localhost:3000:

Terminal
curl http://localhost:3000/
curl http://localhost:3000/health

Copy link to heading5. Deploy to Vercel

Authenticate and create a production deployment:

Terminal
vercel login
vercel deploy --prod

For Git-based deployments, push the directory to a repository, import it in Vercel, and keep both configuration files at the configured project root.

Copy link to headingExtend your application

You have deployed a Fastify API in a Node.js container. You can now add routes, plugins, authentication, and external data services.

Copy link to headingEnvironment variables

Add configuration in the project settings or with vercel env add NAME. Pull development values locally with vercel env pull. Store secrets in environment variables and do not commit them or bake them into the image.

Copy link to headingDatabase and storage

The container runs on Vercel Functions, where the file system is not persistent. For persistent workloads, use Vercel Blob for files, a Marketplace Postgres integration for relational data, a Marketplace Redis integration for sessions and caching, and Vercel Global Config for frequently read configuration.

Keep database pools small and create them outside request handlers so a warm instance can reuse connections.

Copy link to headingIterate with preview deployments

A connected Git repository receives a unique Preview Deployment for each branch push or pull request. Run vercel deploy without --prod to create the same disposable environment from the CLI.

Copy link to headingScale automatically with Fluid compute

Vercel Services use Fluid compute by default. Instances can process concurrent requests and scale to zero between traffic. Active CPU billing stops while code waits on I/O and when no requests are running; memory and invocation charges still apply.

Copy link to headingTroubleshooting

Copy link to headingThe deployment builds but requests return 502

Cause: Fastify is listening on localhost or a hardcoded port such as 3000 instead of the port Vercel routes to.

Fix: Pass host: "0.0.0.0" to listen and parse process.env.PORT, with 80 as the fallback. EXPOSE does not configure routing.

Copy link to headingThe process exits before requests arrive

Cause: The final image is missing server.js, production dependencies, or ESM configuration. An unhandled startup rejection also terminates Node.js.

Fix: Build locally, run the image with PORT=8080, and inspect vercel logs. Keep "type": "module" in package.json when using import syntax.

Copy link to headingDatabase connections increase during traffic spikes

Cause: Each scaled instance creates its own pool, and a pool created inside a request handler creates even more connections.

Fix: Create one small pool at module scope, use a provider's pooled connection string, and place the database near the Function region.

Copy link to headingBackground work stops unexpectedly

Cause: Timers, queues, and detached promises assume the Node.js process remains alive after a response. Instances can pause or terminate when idle between requests.

Fix: Use Vercel Cron for schedules and Workflow for durable multi-step work. Keep request handlers idempotent.

Copy link to headingNext steps

More Vercel Services guides