Dockerize Next.js With Prisma App

Dockerize Next.js With Prisma App

Simple docker file for your Next.js/Node.js app with Prisma

Have you ever been in a situation where you find your ideal platform to deploy your app but you fail to dockerize your app?

Recently I wanted to deploy my SaaS Wildfire on Fly.io and the only method was using Dockerfiles to deploy.

I ran into a bunch of issues and I couldn’t any guide on using Next.js with Prisma on an M1 chip besides a bunch of GitHub issues that weren’t intuitive.

In the end, I did manage to build an image but it was very large and it took me almost half an hour to build.

Ideally, you want your docker images to be as small as possible and when you are using Prisma, you need to run a few commands to generate the Prisma client before you start your Next.js project.

In this guide, we are going to build a lean, simple Docker image for our Next.js and Prisma app. I am going to assume you have failed to dockerize your app and have a working knowledge of docker files.

Note for Mac users on the M1 chip

If you are not on the M1 chip, you can safely skip this part.

The Apple M1 chip, docker, and Prisma don’t seem to get along very well.

When you will run Prisma commands such npx prisma generate on M1 chips with Node alpine base image, it will throw a lot of errors at you for missing libraries or taking an exhaustingly long time(over 20 mins). You can find one such issue here.

Using any other Node image may work but it drastically impacts your image size.

Solution?

Just use remote builder. It isn’t the ideal way but I highly recommend using remote builders because they run on the x86 architecture as opposed to the arm64 of our M1 chips.

Fly.io provides remote builders out-of-the-box for free. You can also set up a GitHub action to build your docker image for you and push it to the Docker hub(Comment on this post if you want a detailed guide on setting it up).

Docker file Below is the entire Dockerfile you can safely copy and experiment with. This Dockerfile uses NPM as the package manager.

FROM node:16-alpine AS builder
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY . .
RUN npm ci
ENV NEXT_TELEMETRY_DISABLED 1
RUN npx prisma migrate deploy
RUN npx prisma generate
RUN npm run build
RUN mkdir -p /app/.next/cache/images
# Production image, copy all the files and run next
FROM node:16-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --chown=nextjs:nodejs --from=builder /app/ ./
USER nextjs
ENV PORT 3000
CMD ["npm", "run","start"]

And the .dockerignore file:

node_modules
.next
.env.local

First of all, we have split the app into two stages, called runner and builder .

This is called multi-stage builds. The core concept is to create a very lean image by only copying the necessary files from builder stage.

Hence, in the builder stage, we install dependencies and build the Next.js app and in the builder stage, we simply copy the files.

Builder stage In the builder stage, we have used Node.js 16 alpine image as a base.

We have also added the libc6-compat .

RUN apk add --no-cache libc6-compat

We need this library because it provides compatibility libraries for glibc which provides the core libraries for the GNU system and GNU/Linux systems, as well as many other systems that use Linux as the kernel.

Next, we run the standard commands to install all the dependencies.

It is important to run Prisma generate commands before the build happens.

RUN npx prisma migrate deploy
RUN npx prisma generate

Make sure you have a .env file in your folder that contains the DATABASE_URL variable.

I tried running the docker image without creating a directory in .next/cache/images but I ran into some issues and hence I had to create the directory explicitly.

Runner stage In the runner stage, we have used the same base image as the builder stage.

We have then set an environment variable NODE_ENV to production and also opted out of Next.js Telemetry collection.

We have also created a user group and user for security purposes and accordingly copied the files from the ./app folder present in the builder stage.

The rest of the file is pretty self-explanatory.

Conclusion Even though the docker file is perfect for deployment and works absolutely well in the production server, I still haven’t been able to figure out how to build this image in my Macbook Air M1.

One potential way of doing this is setting the platform linux/amd64 using the buildx.

docker buildx build --tag [image-tag] -o type=image --platform=linux/arm64,linux/amd64 .

However, even with that, it failed after 20 mins of building image throwing errors regarding memory usage limits and other such issues.

Anyways, I hope these issues are resolved in the near future as Apple is not going to stop pushing its Apple Silicon chips any time soon.

Did you find this article valuable?

Support Anurag Kanoria by becoming a sponsor. Any amount is appreciated!