title: Docker category: Deploy order: 8
import Link from "../../../../components/Link.jsx";
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
<Link name="installation">
## Installation
</Link>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",
};
<Link name="configuration">
## Configuration
</Link>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
<Link name="environment-variables">
## Environment Variables
</Link>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".
When deploying to Kubernetes, configure liveness and readiness probes using the built-in health check endpoints:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
terminationGracePeriodSeconds: 30
containers:
- name: app
image: my-app:latest
ports:
- containerPort: 3000
livenessProbe:
httpGet:
path: /__react_server_health__
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /__react_server_ready__
port: 3000
initialDelaySeconds: 3
periodSeconds: 5
The server automatically handles graceful shutdown on SIGTERM — it stops accepting new connections and drains in-flight requests before exiting. See the HTTP layer page for tuning keep-alive timeouts, request timeouts, and shutdown behavior.
<Link name="how-it-works"> ## How it works </Link>Tip: When running behind an AWS ALB or NLB, the default
keepAliveTimeoutof 65 seconds is configured to exceed the load balancer's 60-second idle timeout, preventing 502 errors under load. You can adjust this in yourreact-server.config.mjsviaserver.keepAliveTimeout.
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)
<Link name="docker-compose">
## Docker Compose
</Link>You can use the generated .docker/ directory with Docker Compose:
services:
app:
build:
context: .docker
ports:
- "3000:3000"
environment:
- NODE_ENV=production
<Link name="troubleshooting">
## Troubleshooting
</Link>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