Deploying a Next.js application with Docker
Introduction
Deploying a Next.js application with Docker involves containerizing the application for consistent and portable deployment. Here's a general approach as below:
Enable Standalone
Output
For a more optimized Docker image, configure Next.js to output a standalone build. This can be done by adding output: standalone
to your next.config.mjs
file:
next.config.mjs
/** * Don't be scared of the generics here. * All they do is to give us autocompletion when using this. * @type {import("next").NextConfig} */ const config = { output: 'standalone', ...otherConfig, }; export default config;
Create a .dockerignore file.
This file specifies files and folders that Docker should ignore when building the image, preventing unnecessary files from being included.
dockerignore
# All node_modules directories node_modules **/node_modules # All secrets **/.env.local **/.env.*.local # By default all git files .git .gitignore .gitattributes .github # Tools caches .cache/* tsconfig.tsbuildinfo .eslintcache # Yarn !.yarnrc.yml !.yarn/plugins !.yarn/releases # Used when building with nextjs (next-eslint) !.eslintrc.base.json !.prettierignore !.prettierrc.js !.eslintignore # npm !.npmrc # Docker related .dockerignore Dockerfile docker-compose.*.yml docker-compose.yml docker # Log files logs *.log # Temp files tmp *.tmp # IDE related .idea .vscode
Create a Dockerfile.
This file contains instructions for building your Docker image. A common multi-stage build approach is used to keep the final image size small.
Dockerfile
FROM node:20-alpine AS base # Install dependencies, Rebuild only when needed FROM base AS deps RUN apk add --no-cache libc6-compat WORKDIR /opt # RUN npm config set registry https://registry.npmmirror.com # Install dependencies based on the preferred package manager COPY package.json yarn.lock* .yarnrc.yml package-lock.json* pnpm-lock.yaml* ./ COPY .yarn/releases/ ./.yarn/releases/ ## For workspaces run yarn link to link workspace. RUN \ if [ -f yarn.lock ]; then yarn; \ elif [ -f package-lock.json ]; then npm ci; \ elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \ else echo "Lockfile not found." && exit 1; \ fi # Rebuild the source code only when needed FROM base AS builder WORKDIR /opt COPY --from=deps /opt/node_modules ./node_modules COPY . . # Next.js collects completely anonymous telemetry data about general usage. # Learn more here: https://nextjs.org/telemetry # Uncomment the following line in case you want to disable telemetry during the build. # ENV NEXT_TELEMETRY_DISABLED 1 # RUN yarn generate RUN yarn build-docker # If using npm comment out above and use below instead # RUN npm run build # Production image, copy all the files and run next FROM base AS runner # RUN apk add --no-cache curl WORKDIR /opt ENV NODE_ENV production # Uncomment the following line in case you want to disable telemetry during runtime. # ENV NEXT_TELEMETRY_DISABLED 1 # RUN addgroup --system --gid 1001 nodejs # RUN adduser --system --uid 1001 nextjs RUN addgroup --system --gid 700 op RUN adduser --system --uid 700 op # Set the correct permission for prerender cache RUN mkdir .next RUN chown op:op .next # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=builder --chown=op:op /opt/.next/standalone ./ COPY --from=builder --chown=op:op /opt/.next/static ./.next/static COPY --from=builder --chown=op:op /opt/public ./public USER op # In most cases, you don't need to change this port. EXPOSE 8080 ENV PORT 8080 # set hostname to localhost ENV HOSTNAME "0.0.0.0" COPY start.sh /opt/start.sh USER root RUN chmod +x /opt/start.sh USER op # server.js is created by next build from the standalone output # https://nextjs.org/docs/pages/api-reference/next-config-js/output CMD sh start.sh
start.sh
# sleep 100000 exec npm run docker:${DeployEnv}
package.json
{ "scripts": { "build-docker": "cross-env NEXT_BUILD_ENV_OUTPUT=standalone next build && yarn postbuild && hyper-next-standalone", "docker": "APP_ENV=prod ./node_modules/@hyperse/hyper-env/bin/hyper-env.mjs -- node server.js", "docker:integration": "APP_ENV=integration ./node_modules/@hyperse/hyper-env/bin/hyper-env.mjs -- node server.js", "docker:prod": "APP_ENV=prod ./node_modules/@hyperse/hyper-env/bin/hyper-env.mjs -- node server.js", "docker:rc": "APP_ENV=rc ./node_modules/@hyperse/hyper-env/bin/hyper-env.mjs -- node server.js", "start": "APP_ENV=prod hyper-env -- next start -p 3000 -H 0.0.0.0", "start:integration": "APP_ENV=integration hyper-env -- next start", "start:prod": "APP_ENV=prod hyper-env -- next start", "start:rc": "APP_ENV=rc hyper-env -- next start" }, "dependencies": { "@hyperse/hyper-env": "^1.0.14" } }
Build the Docker Image.
Navigate to your project's root directory in the terminal and run the following command:
docker build -t your-nextjs-app .
Replace your-nextjs-app
with your desired image name.
Run the Docker Container.
After the image is built, you can run it using:
docker run -p 3000:3000 your-nextjs-app
This command maps port 3000 of the container to port 3000 on your host machine, allowing you to access your Next.js application at http://localhost:3000.
This process creates a Docker image containing your Next.js application, ready for deployment to any environment that supports Docker containers.
Community
We're excited to see the community adopt Hyperse-io, raise issues, and provide feedback. Whether it's a feature request, bug report, or a project to showcase, please get involved!