This blog shows how to deploy a Next.js standalone application integrated with Prisma ORM and PostgreSQL database using Docker for containerization. Next.js offers a powerful framework for building React applications with server-side rendering capabilities, while Prisma simplifies database access with its intuitive ORM. By combining these technologies and leveraging Docker for containerization, you can streamline the deployment process and ensure consistency across different environments.
First of all you need to create Next app with the configuration you like.
npx create-next-app@latest
In the Next.js config file it is needed to have output as standalone. I personally like to use module export next.config.mjs rather than classic next.config.js. But you can do with standard next.config.js file.
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "standalone",
};
export default nextConfig;
You can check the following link: Official Next.js example for building docker image. We will use this configuration that Next has provided to us, but we will have to do several modifications to enable Prisma schema generation.
We will need to add this line:
COPY prisma ./prisma/
With this line we are passing our current prisma schema to the container at the build time so we can generate and update our database.
Also we have to add this line to grant privileges to nextjs user that is used for build.
COPY prisma ./prisma/
After that, we need to update package.json. We will add this line to geneate schema and to start Next.js server.
"prod": "npx prisma migrate deploy && node server.js"
Update official Next.js example to run this command on docker dompose.
CMD ["npm", "run", "prod"]
Complete Dockerfile for running Next.js in container with Prisma should look like this:
# Install dependencies only when needed
FROM base AS deps
# 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
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml\* ./
COPY prisma ./prisma/
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then corepack enable 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 /app
COPY /app/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 npx prisma generate --schema=./prisma/schema.prisma
RUN \
if [ -f yarn.lock ]; then yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
else echo "Lockfile not found." && exit 1; \
fi
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
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
COPY /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY /app/.next/standalone ./
COPY /app/.next/static ./.next/static
COPY prisma ./prisma/
USER nextjs
EXPOSE 3000
ENV PORT 3000
# set hostname to localhost
ENV HOSTNAME "0.0.0.0"
# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD ["npm", "run", "prod"]
Now we can make docker-compose.yaml to build our Next.js image with Prisma attached. I keep those files in the root of the application. This docker-compose file will make image cedo/next and it will map host port :3000 to the container port :3000 where is our Next app running. Also this docker compose file will spin up PostgreSQL database that is listening on default port :5432
version: "3.9"
services:
next:
container_name: next
image: cedo/next
build: .
depends_on:
- db
environment:
- DATABASE_URL=${DATABASE_URL}
ports:
- "3000:3000"
db:
image: postgres:15
container_name: database
restart: always
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USER}
POSTGRES_DB: ${DB_NAME}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 1s
timeout: 3s
retries: 30
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
This setup can be used only with dynamic rendering because the database container is not yet created when building the Next image. That is why you can't have static pages. Migrations are made while developing. You can make dev migrations on your local database and that migration will be applied after the next image has been built.
Another idea is to create a separate database container or use a serverless database. Then you will have your database ready when you build the Next image.
My .env looks like this:
DB_USER=root
DB_PASSWORD=root
DB_NAME=cedo-next
DB_PORT=5432
DB_HOST=localhost
CONTAINER_NAME=database
DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@${CONTAINER_NAME}:${DB_PORT}/${DB_NAME}
If you dont want to build image with docker compose scripit. You can build it yourself and reference it in the compose file.
docker compose up
After you run docker compose up output should look like this: