Deploy with Docker

Run a nextrs app on any container host — Fly.io, Railway, ECS, or a VPS

A nextrs app is a plain Axum binary, so serverful deployment is the boring kind: build a release binary, ship it with the public/ directory, run it behind a reverse proxy. A container works on any host — Fly.io, Railway, Render, ECS, Cloud Run, or a VPS with Docker installed.

The Dockerfile

A standard two-stage build (the repo ships this at the workspace root):

FROM rust:1-bookworm AS builder
WORKDIR /build
COPY . .
RUN cargo build --release -p site

FROM debian:bookworm-slim
RUN apt-get update \
    && apt-get install -y --no-install-recommends ca-certificates \
    && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /build/target/release/site /app/site
COPY site/public /app/public
ENV NEXTRS_PUBLIC_DIR=/app/public
EXPOSE 3000
CMD ["/app/site"]

One detail worth knowing: NEXTRS_PUBLIC_DIR points the binary at the shipped assets. The default asset path is compiled in via CARGO_MANIFEST_DIR, which only exists on the build machine. The env var overrides it at runtime — set it anywhere the binary runs away from its source tree.

Add a .dockerignore with at least target/ and node_modules/ so the build context stays small.

Build and run

docker build -t mysite .
docker run --rm -p 3000:3000 mysite
curl -i http://localhost:3000/

The server binds 0.0.0.0:3000. Map whatever host port you like.

Streaming and the reverse proxy

There's no Vercel adapter in this picture — axum streams chunked text/html natively, so loading shells work out of the box. The one thing that can break streaming is a buffering reverse proxy in front of the container. If you put nginx in front, disable response buffering for the app:

location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_buffering off;
}

Caddy and Traefik stream by default. After deploying, run the smoke test from Streaming — if TTFB equals total time on a loading route, something in the path is buffering.

Static assets

Serverful, the binary serves public/ itself via a router fallback (tower-http ServeDir) — same URLs as the Vercel CDN path, no extra configuration. If you want a CDN in front, point it at the same root URLs; everything under public/ is safe to cache.

Logs and environment

The binary reads .env if present (via dotenvy) and respects RUST_LOG for tracing verbosity (RUST_LOG=info is the default). Container hosts that capture stdout get structured logs with no extra setup.