Home / Articles / Next.js, Tailwind, and Prisma with Docker Postgres

This article is published in English.

Next.js, Tailwind, and Prisma with Docker Postgres

Step-by-step: create a Next.js TypeScript app, run Postgres in Docker, configure Prisma migrate and seed, and query users from a server component.

1108 words

Prerequisites

Install these tools before following the steps:

  • Docker (for a local Postgres container)
  • Node.js (v16+ recommended)
  • npm (ships with Node)

1) Create a Next.js project

Use the official initializer and answer the prompts. Prefer App Router and TypeScript for this walkthrough.

npx create-next-app@latest notes-taking
# choose your options in the interactive prompt (App Router, TypeScript recommended)
cd note-taking

To force TypeScript from the first command:

npx create-next-app@latest note-taking — ts

2) Add Tailwind CSS (Already Come with NextJs)

Recent create-next-app templates already wire Tailwind when you enable it in the interactive options, so no separate Tailwind install is required for a fresh project that selected it.

3) Run Postgres using Docker (locally)

Start Postgres in a container:

docker run — name notes-postgres -e POSTGRES_PASSWORD=password -e POSTGRES_DB=notes_app -p 5432:5432 -d postgres:latest

Flag meanings:

  • docker run — create and start a container
  • --name notes-postgres — stable name for later commands
  • -e POSTGRES_PASSWORD=... / -e POSTGRES_DB=... — bootstrap credentials and database
  • -p 5432:5432 — publish the port to the host
  • -d postgres:latest — detach and use the Postgres image

Confirm it is running:

docker ps

Filter by image:

docker ps — filter “ancestor=postgres”

List every container, including stopped ones:

docker ps -a

Filter by container name:

docker ps — filter “name=notes-postgres”

4) Install Prisma and related packages

From the Next.js project root:

npm install prisma tsx — save-dev
npm install @prisma/extension-accelerate @prisma/client

Initialize Prisma and emit the client under the app folder so App Router imports stay local:

npx prisma init — db — output ../app/generated/prisma

That creates a prisma/ directory with schema.prisma, a .env containing DATABASE_URL, and (with --output) a generated client under app/generated/prisma.

Prefer the default client location instead? Initialize without a custom output:

npx prisma init — db and remove — output.

5) Configure .env (DATABASE_URL)

Point DATABASE_URL at the Docker Postgres instance:

DATABASE_URL=”postgresql://postgres:password@localhost:5432/notes_app?schema=public”

Swap password and database name if you chose different values.

6) Edit prisma/schema.prisma

Use a User/Post model pair that matches the seed script later:

generator client {
provider = "prisma-client-js"
output = "../app/generated/prisma"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model User {
id Int @id @default(autoincrement())
name String
email String @unique
posts Post[]
}

model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
}

Save the file when the models look right.

7) Run migration (create tables)

Apply the first migration to create tables:

npx prisma migrate dev — name init

Prisma writes SQL under prisma/migrations for that change.

8) Seed the database

Seeding inserts sample rows so Prisma Studio and the app have data immediately.

Create prisma/seed.ts (adjust the import if your client output path differs):

// prisma/seed.ts
import { PrismaClient, Prisma } from "../app/generated/prisma";

const prisma = new PrismaClient();

const userData: Prisma.UserCreateInput[] = [
  {
    name: "Alice",
    email: "alice@prisma.io",
    posts: {
      create: [
        {
          title: "Join the Prisma Discord",
          content: "https://pris.ly/discord",
          published: true,
        },
        {
          title: "Prisma on YouTube",
          content: "https://pris.ly/youtube",
        },
      ],
    },
  },
  {
    name: "Bob",
    email: "bob@prisma.io",
    posts: {
      create: [
        {
          title: "Follow Prisma on Twitter",
          content: "https://www.twitter.com/prisma",
          published: true,
        },
      ],
    },
  },
];

export async function main() {
  for (const u of userData) {
    await prisma.user.create({ data: u });
  }
}

main();ty

Register the seed script in package.json:

"prisma": {
"seed": "tsx prisma/seed.ts"
}

Run it:

npx prisma db seed

That inserts Alice and Bob plus their posts.

9) Open Prisma Studio

Inspect and edit rows visually:

npx prisma studio

A browser UI lists models and records.

10) Create a Prisma client wrapper (lib/prisma.ts)

Avoid spawning many PrismaClient instances in development by using a global singleton and the accelerate extension. Create lib/prisma.ts:

// lib/prisma.ts
import { PrismaClient } from "../app/generated/prisma"; // adjust path if needed
import { withAccelerate } from "@prisma/extension-accelerate";

declare global {
// allow global prisma across module reloads in dev
// eslint-disable-next-line no-var
var prisma: PrismaClient | undefined;
}

const prisma =
global.prisma ?? new PrismaClient().$extends(withAccelerate());
if (process.env.NODE_ENV !== "production") global.prisma = prisma;

export default prisma;

Point the import at your generated client path, or use @prisma/client if you skipped --output.

11) Test DB connection from Next.js (server component)

Example app/page.tsx that loads users on the server:

import prisma from '@/lib/db'

export default async function Home() {
  const users = await prisma.user.findMany();
  return (
    <div className="min-h-screen bg-gray-50 flex flex-col items-center justify-center -mt-16">
      <h1 className="text-4xl font-bold mb-8 font-[family-name:var(--font-geist-sans)] text-[#333333]">
        Superblog
      </h1>
      <ol className="list-decimal list-inside font-[family-name:var(--font-geist-sans)]">
        {users.map((user) => (
          <li key={user.id} className="mb-2">
            {user.name}
          </li>
        ))}
      </ol>
    </div>
  );
}

Start the Next.js dev server:

npm run dev

Open http://localhost:3000 and confirm the seeded users render.

Full command checklist

create project

npx create-next-app@latest my-app
cd my-app

postgres docker

docker run — name notes-postgres -e POSTGRES_PASSWORD=password -e POSTGRES_DB=notes_app -p 5432:5432 -d postgres:latest

docker ps

docker exec -it notes-postgres psql -U postgres

prisma

npm install prisma tsx — save-dev
npm install @prisma/extension-accelerate @prisma/client
npx prisma init — db — output ../app/generated/prisma

edit prisma/schema.prisma

npx prisma migrate dev — name init

create prisma/seed.ts

add prisma.seed entry to package.json

npx prisma db seed
npx prisma studio
npm run dev

Troubleshooting & tips

  • Cannot connect to Postgres: confirm the container with docker ps, then verify DATABASE_URL host and port. Try psql from the host or another client against the same URL.
  • Migration failures: read the SQL Prisma generated under prisma/migrations and ensure the database name matches .env.
  • Wrong client import: regenerate after changing output, or switch imports to @prisma/client if you use the default location.
  • Empty UI: re-run the seed and confirm Prisma Studio shows Alice/Bob before debugging the page component.