Nuxt Dockerfile for Production: Nitro Server on Node Alpine
This Nuxt Dockerfile builds your app on node:24-alpine and ships only the self-contained .output folder that Nitro produces. No node_modules are copied, because the build bundles everything the server needs. The server runs as the non-root node user on port 3000, listening on all interfaces.
- CI status
- Build passing
- Image size (uncompressed)
- 174.1 MB
- Build time
- 19 s
- Runtime image
node:24-alpine- Port
- 3000
- Last built
- 2026-10-15
# syntax=docker/dockerfile:1
FROM node:24-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:24-alpine
ENV NODE_ENV=production HOST=0.0.0.0 PORT=3000
WORKDIR /app
COPY --from=build --chown=node:node /app/.output ./.output
USER node
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"].git
node_modules/
.nuxt/
.output/
.env*
Dockerfile
.dockerignoreBefore you build
- Keep the default node-server Nitro preset, which builds to .output/.
- Commit package-lock.json so npm ci installs exact versions.
- Read runtime configuration from NUXT_ prefixed environment variables instead of baking it in at build time.
How this Dockerfile works
FROM node:24-alpine AS buildThe build stage holds Nuxt, Vite and all development dependencies. None of it is needed once the server is built.
COPY package.json package-lock.json ./ RUN npm ciInstalls locked dependencies in a cached layer that's reused until the manifests change.
RUN npm run buildRuns nuxt build, which bundles the server and client into .output with only the dependencies the server actually imports.
ENV NODE_ENV=production HOST=0.0.0.0 PORT=3000Nitro reads HOST and PORT at startup. Binding to 0.0.0.0 makes the server reachable through the container's published port.
COPY --from=build --chown=node:node /app/.output ./.outputCopies the complete server bundle and public assets. This is the only thing the runtime image needs besides Node.
USER node EXPOSE 3000 CMD ["node", ".output/server/index.mjs"]Starts the Nitro server directly with node as the unprivileged user, so stop signals reach the process.
Build and run
docker build -t nuxt-app .
docker run --rm -p 3000:3000 nuxt-appBuild from the project root and open http://localhost:3000. Override runtimeConfig values at runtime with environment variables such as NUXT_PUBLIC_API_BASE.
Other Nuxt Dockerfile variants
The Nitro node-server output is the standard way to self-host a Nuxt app with server rendering. Fully prerendered sites from nuxt generate can be served from Nginx instead, without Node. Verified variants appear in the table below.
| Variant | Runtime image | Image size | CI |
|---|---|---|---|
| Nitro server on Node Alpine | node:24-alpine | 174.1 MB | Passing |
Troubleshooting
- Error: Cannot find module '/app/.output/server/index.mjs'
- The build used a different Nitro preset, such as one for a hosting platform. Remove the preset setting, or set NITRO_PRESET=node-server, then rebuild.
- The app works in the container but not from the host
- The server is listening on localhost only. Keep HOST=0.0.0.0 in the runtime stage and publish the same port as PORT.
- Runtime config changes don't take effect
- Values read at build time are baked into the bundle. Define them in runtimeConfig and override them with NUXT_ prefixed environment variables when starting the container.
FAQ
- Why doesn't the image copy node_modules?
- Nitro traces the server's imports during the build and copies what's needed into .output/server. The full node_modules folder isn't required at runtime.
- Can I use pnpm or Bun instead of npm?
- Yes. Change the lockfile and install command in the build stage. The runtime stage stays the same because it only runs the built .output folder.
- How do I serve a prerendered Nuxt site?
- Run nuxt generate and serve the generated public folder from Nginx. That needs no Node runtime, but it can't render pages on request.