feat: add Docker support with PostgreSQL and health check endpoint

This commit is contained in:
2026-03-29 20:38:56 -07:00
parent 5bd1f2359c
commit bcb967017f
7 changed files with 617 additions and 1 deletions
+370
View File
@@ -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)
+58
View File
@@ -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"]
+73
View File
@@ -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
+68
View File
@@ -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
+6 -1
View File
@@ -13,7 +13,12 @@
"test:acceptance:headed": "playwright test src/__tests__/e2e/ --headed", "test:acceptance:headed": "playwright test src/__tests__/e2e/ --headed",
"db:switch": "node scripts/switch-database.js", "db:switch": "node scripts/switch-database.js",
"db:setup-postgres": "node scripts/setup-postgres.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": { "dependencies": {
"@hookform/resolvers": "^5.2.2", "@hookform/resolvers": "^5.2.2",
+15
View File
@@ -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"
+27
View File
@@ -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 }
);
}
}