From bcb967017f440adaffae48192c5fc29c1c639fb6 Mon Sep 17 00:00:00 2001 From: David Gwilliam Date: Sun, 29 Mar 2026 20:38:56 -0700 Subject: [PATCH] feat: add Docker support with PostgreSQL and health check endpoint --- DOCKER.md | 370 ++++++++++++++++++++++++++++++++++++ Dockerfile | 58 ++++++ docker-compose.override.yml | 73 +++++++ docker-compose.yml | 68 +++++++ package.json | 7 +- scripts/init-postgres.sh | 15 ++ src/app/api/health/route.ts | 27 +++ 7 files changed, 617 insertions(+), 1 deletion(-) create mode 100644 DOCKER.md create mode 100644 Dockerfile create mode 100644 docker-compose.override.yml create mode 100644 docker-compose.yml create mode 100755 scripts/init-postgres.sh create mode 100644 src/app/api/health/route.ts diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..9f4ea0f --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,370 @@ +# Docker Deployment Guide + +This guide covers how to run EuchreCamp using Docker and Docker Compose. + +## Prerequisites + +- Docker Engine 20.10+ +- Docker Compose v2.0+ +- Make (optional, for using Makefile commands) + +## Quick Start + +### 1. Environment Setup + +Create a `.env` file in the project root: + +```bash +cp .env.example .env +``` + +Update the `.env` file with your configuration: + +```env +# Database Configuration (PostgreSQL in Docker) +DATABASE_PROVIDER=postgresql +DATABASE_URL=postgresql://euchre:euchrepassword@postgres:5432/euchre_camp +DATABASE_SHADOW_URL=postgresql://euchre:euchrepassword@postgres:5432/euchre_camp_shadow + +# Better Auth (generate a secure secret for production) +BETTER_AUTH_SECRET=your-secret-key-change-in-production +BETTER_AUTH_URL=http://localhost:3000 + +# Application +NODE_ENV=production +TRUSTED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 +``` + +### 2. Build and Run + +#### Production Mode + +```bash +# Build and start all services +docker-compose up -d + +# View logs +docker-compose logs -f + +# Stop all services +docker-compose down +``` + +#### Development Mode + +```bash +# Use development override configuration +docker-compose -f docker-compose.yml -f docker-compose.override.yml up -d + +# Or use the shorthand +docker-compose --profile dev up -d +``` + +### 3. Access the Application + +- **Web Application**: http://localhost:3000 +- **PostgreSQL**: localhost:5432 (default) or 5433 in development + +## Service Architecture + +``` +┌─────────────────────────────────────────────┐ +│ EuchreCamp │ +├─────────────────────────────────────────────┤ +│ │ +│ ┌──────────────┐ ┌──────────────────┐ │ +│ │ Next.js │◄───│ PostgreSQL │ │ +│ │ App │ │ Database │ │ +│ │ │ │ │ │ +│ │ Port: 3000 │ │ Port: 5432 │ │ +│ └──────────────┘ └──────────────────┘ │ +│ │ +└─────────────────────────────────────────────┘ +``` + +## Service Details + +### App Service + +- **Image**: Multi-stage Next.js build +- **Port**: 3000 +- **Environment**: Production or Development +- **Volumes**: + - `app_data`: Persistent storage for uploads + +### PostgreSQL Service + +- **Image**: postgres:15-alpine +- **Port**: 5432 (production) / 5433 (development) +- **Data Volume**: `postgres_data` (persistent) +- **Health Check**: Uses `pg_isready` + +## Database Management + +### View Database Data + +```bash +# Access PostgreSQL shell +docker exec -it euchre-camp-postgres psql -U euchre -d euchre_camp + +# List tables +\dt + +# Exit +\q +``` + +### Backup Database + +```bash +# Create backup +docker exec euchre-camp-postgres pg_dump -U euchre euchre_camp > backup.sql + +# Restore backup +docker exec -i euchre-camp-postgres psql -U euchre euchre_camp < backup.sql +``` + +### Reset Database + +```bash +# Remove all data and restart +docker-compose down -v +docker-compose up -d +``` + +## Running Commands + +### Run Database Migrations + +```bash +# Apply migrations +docker-compose exec app npx prisma migrate deploy + +# Generate Prisma client +docker-compose exec app npx prisma generate + +# Open Prisma Studio +docker-compose exec app npx prisma studio +``` + +### Create Admin User + +```bash +# Create admin user via script +docker-compose exec app node scripts/create-admin-via-api.js +``` + +### Seed Database + +```bash +# Run seed script +docker-compose exec app node scripts/seed.js +``` + +## Development Workflow + +### Hot Reloading + +Development mode supports hot reloading: + +```bash +# Start in development mode +docker-compose -f docker-compose.yml -f docker-compose.override.yml up -d + +# View logs +docker-compose logs -f app +``` + +### Modifying Code + +Changes to source code are automatically reflected due to volume mounting: + +```bash +# Edit a file locally +nano src/app/page.tsx + +# Next.js will automatically reload +``` + +## Troubleshooting + +### Common Issues + +**1. Database Connection Failed** + +```bash +# Check if PostgreSQL is running +docker ps | grep postgres + +# Check logs +docker logs euchre-camp-postgres + +# Restart services +docker-compose restart postgres +``` + +**2. Port Already in Use** + +```bash +# Check what's using port 3000 +lsof -i :3000 + +# Change port in docker-compose.yml +# ports: +# - "3001:3000" +``` + +**3. Permission Denied** + +```bash +# Ensure scripts are executable +chmod +x scripts/*.sh + +# Check volume permissions +docker exec euchre-camp-app ls -la /app +``` + +**4. Migration Errors** + +```bash +# Reset database +docker-compose down -v +docker-compose up -d + +# Apply migrations manually +docker-compose exec app npx prisma migrate deploy +``` + +### View Logs + +```bash +# All services +docker-compose logs -f + +# Specific service +docker-compose logs -f app +docker-compose logs -f postgres +``` + +### Container Shell Access + +```bash +# App container +docker exec -it euchre-camp-app sh + +# PostgreSQL container +docker exec -it euchre-camp-postgres sh +``` + +## Production Deployment + +### Environment Variables + +For production, ensure you set secure values: + +```env +# Generate a secure secret +BETTER_AUTH_SECRET=$(openssl rand -base64 32) + +# Use real domain +BETTER_AUTH_URL=https://euchrecamp.example.com + +# Trusted origins +TRUSTED_ORIGINS=https://euchrecamp.example.com,https://www.euchrecamp.example.com +``` + +### Security Considerations + +1. **Change default PostgreSQL password**: + ```env + POSTGRES_PASSWORD=your-secure-password + ``` + +2. **Use SSL/TLS for database connections**: + ```env + DATABASE_URL=postgresql://euchre:password@postgres:5432/euchre_camp?sslmode=require + ``` + +3. **Restrict network access**: + - Don't expose PostgreSQL port to public internet + - Use firewall rules to restrict access + +4. **Regular backups**: + - Schedule automated database backups + - Test restoration process + +### Docker Swarm / Kubernetes + +For production orchestration, consider: + +1. **Separate database service** - Use managed PostgreSQL (RDS, Cloud SQL) +2. **Secrets management** - Use Docker secrets or Kubernetes Secrets +3. **Health checks** - Configure proper health checks +4. **Resource limits** - Set CPU and memory limits +5. **Replicas** - Run multiple app instances for high availability + +## Makefile (Optional) + +Create a `Makefile` for convenient commands: + +```makefile +.PHONY: up down logs build shell-migrate + +up: + docker-compose up -d + +down: + docker-compose down + +logs: + docker-compose logs -f + +build: + docker-compose build --no-cache + +shell-app: + docker exec -it euchre-camp-app sh + +shell-db: + docker exec -it euchre-camp-postgres psql -U euchre -d euchre_camp + +migrate: + docker-compose exec app npx prisma migrate deploy + +seed: + docker-compose exec app node scripts/seed.js +``` + +## Performance Tuning + +### PostgreSQL Configuration + +Edit `docker-compose.yml` to tune PostgreSQL: + +```yaml +postgres: + # ... other config ... + command: > + postgres + -c max_connections=200 + -c shared_buffers=256MB + -c effective_cache_size=1GB + -c maintenance_work_mem=64MB + -c checkpoint_completion_target=0.9 + -c wal_buffers=16MB + -c default_statistics_target=100 +``` + +### Next.js Optimization + +The Dockerfile uses multi-stage build for optimal size and performance: + +- **Builder stage**: Installs all dependencies and builds the app +- **Runner stage**: Copies only the built artifacts and production dependencies + +## References + +- [Docker Compose Documentation](https://docs.docker.com/compose/) +- [PostgreSQL Docker Image](https://hub.docker.com/_/postgres) +- [Next.js Deployment](https://nextjs.org/docs/deployment) +- [Prisma Deployment](https://www.prisma.io/docs/concepts/components/prisma-migrate/deployment) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e4e1350 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,58 @@ +# Multi-stage build for EuchreCamp Next.js application + +# Stage 1: Builder +FROM node:20-alpine AS builder + +# Install dependencies +RUN apk add --no-cache python3 make g++ + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies (including dev dependencies for building) +RUN npm ci + +# Copy source code +COPY . . + +# Build the application +RUN npm run build + +# Stage 2: Runner +FROM node:20-alpine AS runner + +# Install dumb-init for proper signal handling +RUN apk add --no-cache dumb-init + +# Create non-root user +RUN addgroup --system --gid 1001 euchre && \ + adduser --system --uid 1001 --ingroup euchre euchre + +# Set working directory +WORKDIR /app + +# Copy built application from builder +COPY --from=builder --chown=euchre:euchre /app/.next ./.next +COPY --from=builder --chown=euchre:euchre /app/public ./public +COPY --from=builder --chown=euchre:euchre /app/package.json ./package.json +COPY --from=builder --chown=euchre:euchre /app/prisma ./prisma + +# Install only production dependencies +RUN npm ci --omit=dev + +# Switch to non-root user +USER euchre + +# Expose port +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1 + +# Start command +ENTRYPOINT ["dumb-init", "--"] +CMD ["npm", "start"] diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 0000000..f0fe33c --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,73 @@ +services: + app: + build: + context: . + target: builder # Use builder stage for development + container_name: euchre-camp-app-dev + ports: + - "3000:3000" + environment: + # Database Configuration for Development + - DATABASE_PROVIDER=postgresql + - DATABASE_URL=postgresql://euchre:euchrepassword@postgres:5432/euchre_camp + - DATABASE_SHADOW_URL=postgresql://euchre:euchrepassword@postgres:5432/euchre_camp_shadow + + # Better Auth Configuration + - BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-dev-secret-key} + - BETTER_AUTH_URL=${BETTER_AUTH_URL:-http://localhost:3000} + + # Application Configuration + - NODE_ENV=development + + # Trusted Origins + - TRUSTED_ORIGINS=${TRUSTED_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000} + + depends_on: + postgres: + condition: service_healthy + + volumes: + # Mount source code for live reload + - .:/app + - /app/node_modules + - /app/.next + - app_data:/app/public/uploads + + command: npm run dev + + restart: unless-stopped + + networks: + - euchre-network + + postgres: + image: postgres:15-alpine + container_name: euchre-camp-postgres-dev + environment: + - POSTGRES_DB=euchre_camp + - POSTGRES_USER=euchre + - POSTGRES_PASSWORD=euchrepassword + ports: + - "5433:5432" # Use different port to avoid conflict with local PostgreSQL + volumes: + - postgres_data_dev:/var/lib/postgresql/data + - ./scripts/init-postgres.sh:/docker-entrypoint-initdb.d/init-postgres.sh + healthcheck: + test: ["CMD-SHELL", "pg_isready -U euchre -d euchre_camp"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + restart: unless-stopped + networks: + - euchre-network + +volumes: + postgres_data_dev: + driver: local + app_data: + driver: local + +networks: + euchre-network: + driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f35bef8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,68 @@ +services: + app: + build: + context: . + target: runner + container_name: euchre-camp-app + ports: + - "3000:3000" + environment: + # Database Configuration + - DATABASE_PROVIDER=postgresql + - DATABASE_URL=postgresql://euchre:euchrepassword@postgres:5432/euchre_camp + - DATABASE_SHADOW_URL=postgresql://euchre:euchrepassword@postgres:5432/euchre_camp_shadow + + # Better Auth Configuration + - BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-your-secret-key-change-in-production} + - BETTER_AUTH_URL=${BETTER_AUTH_URL:-http://localhost:3000} + + # Application Configuration + - NODE_ENV=${NODE_ENV:-production} + + # Trusted Origins for Better Auth + - TRUSTED_ORIGINS=${TRUSTED_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000} + + depends_on: + postgres: + condition: service_healthy + + volumes: + # Persist uploaded files and static content + - app_data:/app/public/uploads + + restart: unless-stopped + + networks: + - euchre-network + + postgres: + image: postgres:15-alpine + container_name: euchre-camp-postgres + environment: + - POSTGRES_DB=euchre_camp + - POSTGRES_USER=euchre + - POSTGRES_PASSWORD=euchrepassword + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./scripts/init-postgres.sh:/docker-entrypoint-initdb.d/init-postgres.sh + healthcheck: + test: ["CMD-SHELL", "pg_isready -U euchre -d euchre_camp"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + restart: unless-stopped + networks: + - euchre-network + +volumes: + postgres_data: + driver: local + app_data: + driver: local + +networks: + euchre-network: + driver: bridge diff --git a/package.json b/package.json index ed15f20..6d98bf7 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,12 @@ "test:acceptance:headed": "playwright test src/__tests__/e2e/ --headed", "db:switch": "node scripts/switch-database.js", "db:setup-postgres": "node scripts/setup-postgres.js", - "db:seed": "node scripts/seed.js" + "db:seed": "node scripts/seed.js", + "docker:up": "docker-compose up -d", + "docker:down": "docker-compose down", + "docker:logs": "docker-compose logs -f", + "docker:build": "docker-compose build", + "docker:shell": "docker exec -it euchre-camp-app sh" }, "dependencies": { "@hookform/resolvers": "^5.2.2", diff --git a/scripts/init-postgres.sh b/scripts/init-postgres.sh new file mode 100755 index 0000000..74f01d3 --- /dev/null +++ b/scripts/init-postgres.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -e + +# This script runs when the PostgreSQL container is first initialized +# It creates the shadow database for Prisma migrations + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + -- Create shadow database for Prisma migrations + CREATE DATABASE "euchre_camp_shadow"; + + -- Grant privileges + GRANT ALL PRIVILEGES ON DATABASE "euchre_camp_shadow" TO "$POSTGRES_USER"; +EOSQL + +echo "PostgreSQL initialization completed" diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts new file mode 100644 index 0000000..3b7c211 --- /dev/null +++ b/src/app/api/health/route.ts @@ -0,0 +1,27 @@ +import { NextResponse } from "next/server"; +import { prisma } from "@/lib/prisma"; + +export async function GET() { + try { + // Check database connection + await prisma.$queryRaw`SELECT 1`; + + return NextResponse.json({ + status: "healthy", + database: "connected", + timestamp: new Date().toISOString(), + }); + } catch (error) { + console.error("Health check failed:", error); + + return NextResponse.json( + { + status: "unhealthy", + database: "disconnected", + error: "Database connection failed", + timestamp: new Date().toISOString(), + }, + { status: 503 } + ); + } +}