nico.fyi
    Published on

    How to deploy a Next.js app with Prisma and Postgres using Coolify

    Authors

    Ever since the drama around Vercel pricing, Coolify has become a popular choice for self-hosting. Coolify makes it easy to deploy web apps on your own server with just a few clicks. It was created to challenge hosting platforms like Vercel, which are famous for their ease of use but tend to be pricey. However, as I suspected, self-hosting is not as easy as some people make it out to be.

    Deploying a static Next.js app via Coolify is indeed straightforward. I moved my Pull Request Best Practices book website from Vercel to my own server using Coolify. You just need to add the repository URL of your project to the Coolify dashboard. Then, you can click the "Deploy" button. Coolify will automatically build your app and deploy it to your server.

    But what if you want to deploy a Next.js app with Prisma and Postgres? It turns out it's not that easy. I encountered several annoying problems, and I'm going to share my experience with you so that you can learn from it.

    First, I installed and deployed a Postgres database from the Coolify dashboard. Once it was running, Coolify exposed the internal URL of the Postgres instance, which I used as an environment variable for the Next.js app. I then tried to deploy the app via the Coolify dashboard and immediately hit the first problem.

    Before building the app, my project needed to run Prisma migration first. But for some reason, it could not reach the database instance during the build.

    13 1.227 > prisma migrate deploy
    13 1.227
    13 2.087 Environment variables loaded from .env
    13 2.088 Prisma schema loaded from prisma/schema.prisma
    13 2.092 Datasource "db": PostgreSQL database "postgres", schema "public" at "ycskg0w:5432"
    13 2.195
    13 2.196 Error: P1001: Can't reach database server at `ycskg0w:5432`
    13 2.196
    13 2.196 Please make sure your database server is running at `ycskg0w:5432`.
    

    I ensured the database was running and accessible. So I assumed the error occurred because the build container wasn't in the same network as the database container. Unfortunately, I forgot what I did next, but I managed to configure the build container to reach the database container.

    However, I soon encountered another problem: I got a "Schema engine error" without any further information. I found other people had a similar problem because Nixpacks doesn't support Prisma yet.

    Eventually, I managed to deploy the app by ditching Nixpacks and using the following Dockerfile instead:

    Dockerfile
    FROM node:18-alpine AS base
    
    # Step 1. Rebuild the source code only when needed
    FROM base AS builder
    
    WORKDIR /app
    
    # Install dependencies based on the preferred package manager
    COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
    # Omit --production flag for TypeScript devDependencies
    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; \
        else echo "Warning: Lockfile not found. It is recommended to commit lockfiles to version control." && yarn install; \
        fi
    
    # Adjust the files and folders that should be copied to the build container
    COPY app ./app
    COPY public ./public
    COPY components ./components
    COPY lib ./lib
    COPY next.config.mjs .
    COPY prisma ./prisma
    COPY components.json .
    COPY tailwind.config.ts .
    COPY tsconfig.json .
    COPY postcss.config.mjs .
    
    # Environment variables must be present at build time
    ARG DATABASE_URL
    ENV DATABASE_URL=${DATABASE_URL}
    ARG BASIC_AUTH_USER
    ENV BASIC_AUTH_USER=${BASIC_AUTH_USER}
    ARG BASIC_AUTH_PASSWORD
    ENV BASIC_AUTH_PASSWORD=${BASIC_AUTH_PASSWORD}
    
    # Build Next.js based on the preferred package manager
    RUN \
        if [ -f yarn.lock ]; then yarn build; \
        elif [ -f package-lock.json ]; then npm run build; \
        elif [ -f pnpm-lock.yaml ]; then pnpm build; \
        else npm run build; \
        fi
    
    # Step 2. Production image, copy all the files and run next
    FROM base AS runner
    
    RUN apk --no-cache add curl
    
    WORKDIR /app
    
    RUN addgroup --system --gid 1001 nodejs
    RUN adduser --system --uid 1001 nextjs
    USER nextjs
    
    COPY --from=builder /app/public ./public
    
    # Automatically leverage output traces to reduce image size
    COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
    COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
    
    # Environment variables must be redefined at run time
    ARG DATABASE_URL
    ENV DATABASE_URL=${DATABASE_URL}
    ARG BASIC_AUTH_USER
    ENV BASIC_AUTH_USER=${BASIC_AUTH_USER}
    ARG BASIC_AUTH_PASSWORD
    ENV BASIC_AUTH_PASSWORD=${BASIC_AUTH_PASSWORD}
    
    EXPOSE 3000
    
    ENV PORT 3000
    
    CMD HOSTNAME=0.0.0.0 node server.js
    

    Here were the important steps:

    1. I need to set the Next.js build output as standalone by updating the next.config.mjs file.
    const nextConfig = {
      output: 'standalone',
      // ...
    }
    
    1. I need to set the DATABASE_URL environment variable to the internal URL of the Postgres database instance exposed by Coolify.
    2. In the Coolify dashboard of the project, select "Dockerfile" as the Build Pack.
    3. Add that Dockerfile to the repository and push it to the repository. Coolify will automatically build the app and deploy it to your server successfully.

    You can find the repository of the project here and the deployed app here.

    Should you self-host?

    While I learned several things from this experience and from my previous experience deploying Minio via Coolify, I maintain my position that self-hosting is never easy. Tools like Coolify indeed make it easier to deploy web apps, but they are not a silver bullet. You still need experience with server administration and networking to deploy your own server and fix any issues that may arise. You also need patience.

    My experience with Coolify in this blog post was for the sake of experimentation and learning. But if I needed to deploy a production app, I would have just used Vercel. I don't want to encounter issues that could derail me from what really matters: shipping a product, getting feedback, and iterating.

    I suggest you find out if you should self-host or use Vercel for hosting your Next.js app by answering the questions in this small tool.


    Are you working in a team environment and your pull request process slows your team down? Then you have to grab a copy of my book, Pull Request Best Practices!