Managing .env* files in Next.js + Prisma + PostgreSQL
Managing .env, .env.development, .env.development.local, and .env.example in Next.js + Prisma + PostgreSQL
When working on a Next.js project with Prisma ORM and PostgreSQL, environment file confusion is one of the most common sources of bugs—especially when switching between local development (next dev), production builds (next build), and Prisma CLI commands (npx prisma migrate).
Unlike Next.js, the Prisma CLI does not automatically read Next.js-specific files like .env.development.local (and in Prisma 7+, environment loading is configured explicitly via prisma.config.ts or CLI wrappers). If you don't structure your .env files properly, next dev will connect to your local database while npx prisma migrate dev fails with a missing DATABASE_URL error—or worse, runs against your production server database.
This guide covers:
- What each
.envfile does in Next.js - Which files belong in Git and which must be ignored
- How to separate local and production server PostgreSQL databases safely
- How to run Prisma CLI commands reliably across both environments
1. Understanding the Four .env Files
Next.js loads environment variables in a strict priority order (top overrides bottom):
| File | Loaded When | Purpose |
|---|---|---|
.env.development.local | next dev only | Your personal local secrets (local Postgres credentials, local OAuth keys, test API keys). Overrides everything during local development. |
.env.production.local | next build / next start | Production server secrets (live server Postgres URL, live SMTP/OAuth secrets). Lives only on your production server. |
.env.local | All environments (except test) | General local overrides. Avoid putting DATABASE_URL here if you run next build locally, as it affects both dev and prod. |
.env.development | next dev only | Safe, non-secret defaults shared across all developers (e.g., NEXT_PUBLIC_APP_URL="http://localhost:3000"). |
.env | All environments (Fallback) | Base fallback values—and the default file Prisma CLI reads automatically. |
.env.example | Never loaded automatically | A committed template documenting every required variable with dummy values. |
2. Which Files Should Be in Git vs. Ignored?
A clean rule of thumb: anything ending in .local or containing real credentials stays out of Git; templates and non-secret defaults stay in Git.
| File | Commit to Git? | Why? |
|---|---|---|
.env.example | YES | Serves as living documentation so you (or any teammate/CI pipeline) know every variable needed to boot the app. |
.env.development | YES (only if secret-free) | Contains safe local defaults like NODE_ENV=development or NEXT_PUBLIC_APP_URL=http://localhost:3000. Never put passwords or API keys here. |
.env.development.local | NO (Git Ignored) | Contains your personal local PostgreSQL password, local BETTER_AUTH_SECRET, and dev API keys. |
.env.production.local | NO (Git Ignored) | Contains live production database passwords and keys. Created directly on your server via SSH. |
.env | NO (Git Ignored) | Because Prisma CLI reads .env by default, developers often keep active local credentials here. Always ignore .env in .gitignore to prevent accidental secret leaks. |
Recommended .gitignore Rules
Make sure your .gitignore explicitly ignores .env and all .local variants while allowing .env.example and .env.development:
# Ignore all .env files with secrets .env .env*.local # Explicitly allow safe shared templates/defaults !.env.example !.env.development
3. Setting Up Local vs. Production Server Databases
To avoid ever mixing up your local Mac/PC PostgreSQL instance with your live Ubuntu/VPS PostgreSQL database, separate them cleanly:
A. .env.example (Committed to Git)
Keep this updated whenever you add a new integration:
# App NODE_ENV="development" NEXT_PUBLIC_APP_URL="http://localhost:3000" # Database (PostgreSQL) DATABASE_URL="postgresql://postgres:password@localhost:5432/myapp_dev?schema=public" # Auth & External Services BETTER_AUTH_SECRET="replace-with-32-char-random-string" GOOGLE_CLIENT_ID="your-google-client-id" GOOGLE_CLIENT_SECRET="your-google-client-secret"
B. .env.development (Committed to Git — Non-Secrets Only)
NEXT_PUBLIC_APP_URL="http://localhost:3000"
C. .env.development.local (Local Machine Only — Ignored by Git)
Used by next dev on your local machine:
DATABASE_URL="postgresql://localuser:localpass@localhost:5432/myapp_dev?schema=public" BETTER_AUTH_SECRET="local-dev-secret-key-at-least-32-chars" GOOGLE_CLIENT_ID="dev-client-id.apps.googleusercontent.com" GOOGLE_CLIENT_SECRET="GOCSPX-dev-secret"
D. Production Server Setup (.env.production.local on Server Only)
On your production server (e.g., an Ubuntu VM managed with PM2 and Nginx), create .env.production.local directly in your project directory (or inject variables via Docker Compose / PM2 ecosystem config):
NODE_ENV="production" NEXT_PUBLIC_APP_URL="https://example.com" DATABASE_URL="postgresql://produser:strong_prod_password@127.0.0.1:5432/myapp_prod?schema=public" BETTER_AUTH_SECRET="production-random-64-char-secret" GOOGLE_CLIENT_ID="prod-client-id.apps.googleusercontent.com" GOOGLE_CLIENT_SECRET="GOCSPX-prod-secret"
Security Tip: Never put your production
DATABASE_URLon your local machine unless you are actively running an intentional, temporary maintenance script against it.
4. How to Run Prisma Commands Properly
Here is the biggest gotcha with Next.js + Prisma:
npx prisma ... does not load .env.development.local or .env.production.local automatically.
You have three clean ways to handle this depending on your Prisma version and preference:
Option 1: Install dotenv-cli (Recommended for Explicit Control)
Install dotenv-cli as a dev dependency so you can explicitly point Prisma at whichever .env file you want:
npm install -D dotenv-cli
Add helper scripts to your package.json:
{ "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "db:migrate:dev": "dotenv -e .env.development.local -- npx prisma migrate dev", "db:push:dev": "dotenv -e .env.development.local -- npx prisma db push", "db:studio": "dotenv -e .env.development.local -- npx prisma studio", "db:generate": "npx prisma generate", "db:migrate:prod": "dotenv -e .env.production.local -- npx prisma migrate deploy" } }
Now every command explicitly targets the exact database you intend:
- Local schema migration:
npm run db:migrate:dev(reads.env.development.local) - Local Prisma Studio GUI:
npm run db:studio(reads.env.development.local) - Generate Prisma Client types:
npm run db:generate(does not need a DB connection) - Production server migration:
npm run db:migrate:prod(reads.env.production.localon the server)
Option 2: Use @next/env inside prisma.config.ts (Prisma 7+)
If you are on Prisma 7+, Prisma uses prisma.config.ts to resolve environment variables. You can use Next.js's built-in @next/env package so Prisma uses the exact same .env* loading order as Next.js:
import { loadEnvConfig } from "@next/env"; import { defineConfig } from "prisma/config"; // Automatically loads .env.development.local in dev, or .env.production.local when NODE_ENV=production loadEnvConfig(process.cwd()); export default defineConfig({ schema: "prisma/schema.prisma", datasource: { url: process.env.DATABASE_URL!, }, });
Option 3: Symlink .env Locally and on Server
If you are on Prisma 5/6 and prefer running raw npx prisma migrate dev without dotenv-cli, you can symlink .env to .env.development.local on your local machine (and to .env.production.local on your server):
# On your local machine: ln -sf .env.development.local .env # On your production server: ln -sf .env.production.local .env
Because .env is in .gitignore, both Next.js and Prisma CLI stay aligned without risking Git commits.
5. Production Deployment Workflow Checklist
When deploying updates from your local machine to your production server:
- Locally: Make schema changes in
prisma/schema.prismaand run:
This creates a new SQL migration file insidenpm run db:migrate:dev -- --name add_user_creditsprisma/migrations/and applies it to your local PostgreSQL database. - Commit & Push: Commit
prisma/schema.prisma, the generatedprisma/migrations/folder, and your application code to Git. - On Production Server: Pull the latest code, apply pending migrations safely (never use
migrate devin production), rebuild, and restart:git pull origin main npm install npx prisma generate npm run db:migrate:prod npm run build pm2 restart all
(Note: prisma migrate deploy only applies committed migration files—it never resets data or creates shadow databases, making it safe for production.)