| title | Containerising Next.js using Docker and Bun | |||||
|---|---|---|---|---|---|---|
| date | 2023-09-17 | |||||
| tags |
|
|||||
| draft | false | |||||
| summary | Using the newest JavaScript runtime with Next.js and Docker |
- Intro
- Installing Bun and scaffolding a new Next.js project
- Containerising our new Next.js project
- Full Dockerfile
- Building the Docker image and running it as a container
Bun is a new performant JavaScript runtime and toolkit containing its own bundler, test runner and package manager, while aiming to be a drop-in replacement for existing projects using Node.js. At the time of writing it has reached version 1.0.2, and I had the chance to test it with some personal and work projects, including using it in combination with Next.js in Docker containers.
So far I have been really impressed with its performance and its compatibility with existing projects. Below I am showing you how to containerise a Next.js project using Bun, with minimal changes from the official documentation.
The official documentation offers many alternatives on how to install Bun, the quickest is:
curl -fsSL https://bun.sh/install | bashAfter that, creating a new Next.js project is as easy as:
bunx create-next-appVercel offers guidelines in their official documentation on how to create a Docker image along with a sample repository.
-
First we need to modify the
next.config.jsfile so that the build output can bestandalone, as shown in the official documentation.// next.config.js module.exports = { // ... rest of the configuration. output: 'standalone', }
After that, we can reuse the existing example by making the following modifications in the provided Dockerfile:
-
The official base image is defined as
FROM oven/bun AS baseand is released by Oven, "the company behind Bun" in DockerHub. -
Bunis using by default a binary lockfilebun.lockbfor performance. In order to install a project's dependencies we need to copy first thepackage.jsonand the generatedbun.lockblockfile.COPY package.json bun.lockb ./ -
Installing using reproducible dependencies can be done with:
bun install --frozen-lockfileRUN bun install --frozen-lockfile -
The build step can be run using:
bun run buildRUN bun run build -
The base image is creating a unix group called
bun. We don't need to create a new separate groupnodejsas it was done in the previous version of the Dockerfile. We only need to change the ownership of the generated files from the build step tonextjs:bun.RUN chown nextjs:bun .next COPY --from=builder --chown=nextjs:bun /app/.next/standalone ./ COPY --from=builder --chown=nextjs:bun /app/.next/static ./.next/static
-
Finally, the server can be run with:
bun server.jsCMD ["bun", "server.js"]
-
Optionally, my personal preference is to disable the collection of anonymous telemetry during the build step and for the final produced Docker image.
ENV NEXT_TELEMETRY_DISABLED 1
The full Dockerfile with the above changes can now be added to the root of the Next.js project and is as followed:
FROM oven/bun AS base
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
# Install dependencies
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Disable telemetry during the build
ENV NEXT_TELEMETRY_DISABLED 1
RUN bun run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Disable telemetry
ENV NEXT_TELEMETRY_DISABLED 1
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:bun .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:bun /app/.next/standalone ./
COPY --from=builder --chown=nextjs:bun /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
# Set hostname to localhost
ENV HOSTNAME "0.0.0.0"
CMD ["bun", "server.js"]As before, the image can be built and tagged using:
docker build -t my-app .Finally, running a container of the image:
docker run -p 3000:3000 my-app