DeployEdit this page

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

Build your application and create the Docker image in one step:

pnpm react-server build [root] --deploy

This will:

  1. Build your application (edge build for Bun/Deno, standard build for Node.js)
  2. Copy server output and static assets; trace dependencies with @vercel/nft (Node.js only)
  3. Generate a Dockerfile, .dockerignore, and package.json in the .docker/ output directory
  4. 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:

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:

  1. For Node.js runtime: copies a custom HTTP server entry that serves static files and delegates to @lazarv/react-server/node for SSR, then uses @vercel/nft to trace all required node_modules dependencies
  2. For Bun/Deno runtime: performs an edge build that bundles everything into a single file, and generates a start script with inlined static routes
  3. Copies static assets (JS, CSS, images) and server build output (RSC payloads, server components)
  4. Generates a single-stage Dockerfile with the appropriate base image (node, oven/bun, or denoland/deno)
  5. For Node.js: uses tini as the init process for proper signal handling
  6. 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