Deploying Next.js and PostgreSQL with Docker
Complete guide to deploying Next.js and PostgreSQL applications with Docker including multi-container setup, docker-compose, environment variables, and production deployment.
# Deploying Next.js and PostgreSQL with Docker
Introduction
Deploying a Next.js application with PostgreSQL using Docker involves containerizing both the frontend/backend application and the database, connecting them via Docker networking, and managing environment variables and persistent data. This tutorial provides a complete production-ready setup.
Learning Objectives
By the end of this tutorial, you will be able to:
- Containerize a Next.js application
- Run PostgreSQL in a Docker container
- Connect Next.js to PostgreSQL via Docker networking
- Use docker-compose for multi-container orchestration
- Manage environment variables and secrets
- Configure persistent data for PostgreSQL
- Deploy to production
Technical Background
Architecture
Next.js Container (port 3000)
|
| (Docker network: app-network)
v
PostgreSQL Container (port 5432)
|
v
Named Volume: postgres_dataPrerequisites
- Docker and Docker Compose installed
- Next.js application code
- Understanding of environment variables
Hardware and Software Requirements
| Component | Requirement | |-----------|-------------| | Docker | v24+ | | Docker Compose | v2+ | | Node.js | v20+ (for local development) |
Step-by-Step Implementation
Phase 1: Next.js Dockerfile
# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup -S nodejs && adduser -S nextjs -G nodejs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]Phase 2: Docker Compose Configuration
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/mydb
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
- NEXTAUTH_URL=http://localhost:3000
depends_on:
db:
condition: service_healthy
networks:
- app-network
restart: unless-stopped
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
networks:
- app-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d mydb"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
redis:
image: redis:7-alpine
ports:
- "6379:6379"
networks:
- app-network
restart: unless-stopped
networks:
app-network:
driver: bridge
volumes:
postgres_data:Phase 3: Environment Configuration
# .env file
NEXTAUTH_SECRET=your-secret-key-here
NEXTAUTH_URL=http://localhost:3000
DATABASE_URL=postgresql://postgres:password@db:5432/mydb# Load environment variables
export $(cat .env | xargs)
# Or use with docker compose
docker compose --env-file .env up -dPhase 4: Next.js Configuration
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
reactStrictMode: true,
}
module.exports = nextConfig// lib/db.ts
import { Pool } from 'pg'
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
})
export async function query(text: string, params?: any[]) {
const client = await pool.connect()
try {
const result = await client.query(text, params)
return result
} finally {
client.release()
}
}Phase 5: Deploy with Docker Compose
# Build and start all services
docker compose up -d --build
# View logs
docker compose logs -f app
# Check service health
docker compose ps
# Stop all services
docker compose down
# Stop and remove volumes
docker compose down -vPhase 6: Database Migrations
# Run migrations inside app container
docker compose exec app npx prisma migrate deploy
# Or run standalone migration
docker compose run --rm app npx prisma migrate deployPhase 7: Production Deployment
# docker-compose.prod.yml
version: '3.8'
services:
app:
image: myregistry.com/myapp:prod
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@db:5432/mydb
secrets:
- db_password
- nextauth_secret
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 10s
restart_policy:
condition: on-failure
db:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
volumes:
- postgres_data:/var/lib/postgresql/data
secrets:
db_password:
file: ./secrets/db_password
nextauth_secret:
file: ./secrets/nextauth_secret
volumes:
postgres_data:# Deploy with production config
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -dExpected Output
- Next.js application containerized and running
- PostgreSQL database with persistent data
- Services communicating via Docker network
- Environment variables configured
- Production deployment working
Verification Procedures
| Check | Command | Expected Result | |-------|---------|-----------------| | Services running | `docker compose ps` | All services healthy | | App accessible | `curl http://localhost:3000` | HTTP 200 | | Database connected | `docker compose exec app npx prisma db execute "SELECT 1"` | Query succeeds | | Logs | `docker compose logs app` | No errors |
Troubleshooting Scenarios
| Issue | Diagnosis | Resolution | |-------|-----------|------------| | App can't connect to DB | Wrong DATABASE_URL or DB not ready | Verify env var; check DB healthcheck | | Build fails | Missing dependencies or build error | Check Dockerfile; verify package.json | | Data lost on restart | No volume for PostgreSQL | Add postgres_data volume | | Container exits immediately | Application crash | Check container logs |
Best Practices
- **Use standalone output:** Next.js standalone for smaller Docker images
- **Health checks:** Configure PostgreSQL healthcheck
- **Persistent volumes:** Use named volumes for database data
- **Environment variables:** Keep secrets out of code; use .env or Docker secrets
- **Non-root user:** Run Next.js as non-root user
- **Resource limits:** Set memory and CPU limits in production
Security Considerations
- **Database password:** Use Docker secrets or environment variables, not hardcoded
- **NEXTAUTH_SECRET:** Use strong random secret
- **Non-root user:** Run containers as non-root
- **Network isolation:** Use custom Docker network
- **Image scanning:** Scan for vulnerabilities before deployment
Conclusion
Deploying Next.js and PostgreSQL with Docker provides a portable, reproducible deployment pattern. By containerizing both the application and database, using docker-compose for orchestration, and following security best practices, you can run full-stack applications reliably in any environment.
References
- Next.js Docker: https://nextjs.org/docs/deployment#docker-contai
- PostgreSQL Docker: https://hub.docker.com/_/postgres
- Docker Compose: https://docs.docker.com/compose/
- Next.js Standalone: https://nextjs.org/docs/advanced-features/output-file-tracing