Docker
To deploy with Docker, use the built-in docker adapter. This adapter creates a production-ready Docker image running your application on Node.js with proper signal handling, static file serving, and a minimal Alpine-based image.
You need Docker installed on your machine.
docker --version
No additional packages are needed — the adapter is built into @lazarv/react-server.
Add the adapter to your react-server.config.mjs file:
export default {
adapter: "docker",
};
You can customize the adapter by passing options:
export default {
adapter: [
"docker",
{
runtime: "node", // Runtime: "node" (default), "bun", or "deno"
name: "my-app", // Application name (default: from package.json)
tag: "my-app:v1.0", // Docker image tag (default: "<name>:latest")
port: 8080, // Port to expose (default: 3000)
version: "22-alpine", // Base image tag (default varies by runtime)
},
],
};
Configuration Options
runtime: The runtime to use inside the Docker container."node"(default),"bun", or"deno". Node.js uses@lazarv/react-server/nodewith dependency tracing. Bun and Deno use edge builds bundled into a single file.name: Application name used for the Docker image. Falls back topackage.jsonname (without scope) or"react-server-app".tag: Docker image tag used when building. Defaults to"<name>:latest".port: Port the server listens on inside the container. Defaults to3000.version: Docker base image tag. Defaults to"20-alpine"for Node.js,"alpine"for Bun and Deno.
Build your application and create the Docker image in one step:
pnpm react-server build [root] --deploy
This will:
- Build your application (edge build for Bun/Deno, standard build for Node.js)
- Copy server output and static assets; trace dependencies with
@vercel/nft(Node.js only) - Generate a
Dockerfile,.dockerignore, andpackage.jsonin the.docker/output directory - Build the Docker image
You can also build and deploy separately:
# Build the application
pnpm react-server build [root]
# Build the Docker image manually
docker build -t my-app:latest .docker
Then run the container:
docker run -p 3000:3000 my-app:latest
The following environment variables are set in the generated Dockerfile:
NODE_ENV=production— ensures React uses production bundlesPORT— the port the server listens on (default:3000)
You can pass additional environment variables at runtime:
docker run -p 3000:3000 -e MY_API_KEY=secret my-app:latest
Or override the port:
docker run -p 8080:8080 -e PORT=8080 my-app:latest
If you build with --sourcemap, the Dockerfile will also set NODE_OPTIONS="--enable-source-maps".
The adapter:
- For Node.js runtime: copies a custom HTTP server entry that serves static files and delegates to
@lazarv/react-server/nodefor SSR, then uses@vercel/nftto trace all requirednode_modulesdependencies - For Bun/Deno runtime: performs an edge build that bundles everything into a single file, and generates a start script with inlined static routes
- Copies static assets (JS, CSS, images) and server build output (RSC payloads, server components)
- Generates a single-stage Dockerfile with the appropriate base image (
node,oven/bun, ordenoland/deno) - For Node.js: uses tini as the init process for proper signal handling
- Runs as a non-root user for security (Node.js and Bun)
Output structure
Node.js runtime:
.docker/
├── Dockerfile
├── .dockerignore
├── package.json
├── server/
│ ├── index.mjs # Server entry point
│ ├── .react-server/ # Build output (RSC, server components)
│ └── node_modules/ # Traced dependencies only
└── static/ # Static assets (JS, CSS, images)
Bun/Deno runtime:
.docker/
├── Dockerfile
├── .dockerignore
├── package.json
├── start.mjs # Generated start script with static routes
├── server/
│ └── .react-server/ # Build output (edge bundle)
└── static/ # Static assets (JS, CSS, images)
You can use the generated .docker/ directory with Docker Compose:
services:
app:
build:
context: .docker
ports:
- "3000:3000"
environment:
- NODE_ENV=production
Missing modules at runtime (Node.js only)
The Node.js runtime uses @vercel/nft to trace dependencies. If a module is loaded dynamically (e.g., via createRequire()) it may not be detected. Check the container logs for MODULE_NOT_FOUND errors and ensure the required packages are listed in your package.json dependencies. This does not apply to Bun/Deno runtimes, which use a single bundled file.
Container doesn't stop with Ctrl+C (Node.js only)
The Node.js Dockerfile uses tini as the init process. If you're running docker run without -it, signals may not be forwarded. Use:
docker run -it -p 3000:3000 my-app:latest
Port conflicts
If port 3000 is already in use, map to a different host port:
docker run -p 8080:3000 my-app:latest