Deploying Your Next.js App Standalone with PostgreSQL, Prisma and Docker
Published on: 16/02/2024
Time to read: 7 mins

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.


Create next app

First of all you need to create Next app with the configuration you like.

Terminal
npx create-next-app@latest

Update next.config.mjs

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.

next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: "standalone",
};

export default nextConfig;

Docker configuration

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 --chown=nextjs:nodejs prisma ./prisma/

After that, we need to update package.json. We will add this line to geneate schema and to start Next.js server.

package.json
"prod": "npx prisma migrate deploy && node server.js"

Update official Next.js example to run this command on docker dompose.

Dockerfile
CMD ["npm", "run", "prod"]

Complete Dockerfile for running Next.js in container with Prisma should look like this:

Dockerfile

# 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 --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

# 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 --from=builder /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 --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --chown=nextjs:nodejs 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

docker-compose.yaml
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:

.env
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.

Terminal
docker compose up

After you run docker compose up output should look like this:


Next standalone, doker, prisma, postgres


Loading comments