From b8d4854c462d7578df397e0f5f00458df8e1b5a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Kadir=20Ak=C4=B1n?= Date: Mon, 5 Jan 2026 22:57:13 +0300 Subject: [PATCH] feat(docker): Add Docker deployment guide and configuration instructions --- .dockerignore | 51 +++++ .github/workflows/docker-publish.yml | 62 ++++++ DOCKER.md | 298 +++++++++++++++++++++++++++ docker-compose.yml | 46 +++++ docker/Dockerfile | 91 ++++++++ docker/Dockerfile.app | 68 ++++++ docker/entrypoint-app.sh | 24 +++ docker/entrypoint.sh | 109 ++++++++++ docker/supervisord.conf | 28 +++ next.config.ts | 2 +- src/app/api/health/route.ts | 30 +++ 11 files changed, 808 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/docker-publish.yml create mode 100644 DOCKER.md create mode 100644 docker-compose.yml create mode 100644 docker/Dockerfile create mode 100644 docker/Dockerfile.app create mode 100644 docker/entrypoint-app.sh create mode 100644 docker/entrypoint.sh create mode 100644 docker/supervisord.conf create mode 100644 src/app/api/health/route.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..3bd0b97a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,51 @@ +# Dependencies +node_modules +npm-debug.log* + +# Build outputs +.next +out +dist + +# Git +.git +.gitignore + +# IDE +.vscode +.idea +*.swp +*.swo + +# Environment files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Docker +docker-compose*.yml +Dockerfile* + +# Development files +.github +.claude +packages +*.md +!README.md + +# Test files +coverage +.nyc_output + +# OS files +.DS_Store +Thumbs.db + +# Logs +logs +*.log + +# Prisma +prisma/migrations/**/migration_lock.toml diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 00000000..5db9e300 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,62 @@ +name: Build and Publish Docker Image + +on: + push: + branches: + - main + tags: + - 'v*' + pull_request: + branches: + - main + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository_owner }}/prompts.chat + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + platforms: linux/amd64,linux/arm64 diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 00000000..ce7ecc24 --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,298 @@ +# Docker Deployment Guide + +Run prompts.chat with a single command using Docker. + +## Quick Start (All-in-One Image) + +The easiest way to run prompts.chat - a single container with Node.js and PostgreSQL: + +```bash +docker run -d \ + --name prompts \ + -p 80:80 \ + -v prompts-data:/data \ + ghcr.io/f/prompts.chat +``` + +Open http://localhost in your browser. + +## Configuration + +### Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `AUTH_SECRET` | Secret for authentication tokens | Auto-generated | +| `PORT` | Port to run the app on | `80` | +| `DATABASE_URL` | PostgreSQL connection string | Internal DB | + +### Production Setup + +For production, always set `AUTH_SECRET`: + +```bash +docker run -d \ + --name prompts \ + -p 80:80 \ + -v prompts-data:/data \ + -e AUTH_SECRET="your-secret-key-min-32-chars-long" \ + ghcr.io/f/prompts.chat +``` + +Generate a secure secret: +```bash +openssl rand -base64 32 +``` + +### With OAuth Providers + +Enable GitHub/Google authentication: + +```bash +docker run -d \ + --name prompts \ + -p 80:80 \ + -v prompts-data:/data \ + -e AUTH_SECRET="your-secret-key" \ + -e AUTH_GITHUB_ID="your-github-client-id" \ + -e AUTH_GITHUB_SECRET="your-github-client-secret" \ + -e AUTH_GOOGLE_ID="your-google-client-id" \ + -e AUTH_GOOGLE_SECRET="your-google-client-secret" \ + ghcr.io/f/prompts.chat +``` + +### With AI Search (OpenAI) + +Enable semantic search with OpenAI embeddings: + +```bash +docker run -d \ + --name prompts \ + -p 80:80 \ + -v prompts-data:/data \ + -e AUTH_SECRET="your-secret-key" \ + -e OPENAI_API_KEY="sk-..." \ + ghcr.io/f/prompts.chat +``` + +## Docker Compose (Separate Containers) + +For more control, use Docker Compose with separate app and database containers: + +```bash +# Clone the repository +git clone https://github.com/f/awesome-chatgpt-prompts.git +cd awesome-chatgpt-prompts + +# Create .env file +echo "AUTH_SECRET=$(openssl rand -base64 32)" > .env + +# Start services +docker compose up -d +``` + +### docker-compose.yml + +```yaml +services: + app: + build: + context: . + dockerfile: docker/Dockerfile.app + ports: + - "80:3000" + environment: + - DATABASE_URL=postgresql://prompts:prompts@db:5432/prompts + - AUTH_SECRET=${AUTH_SECRET} + depends_on: + db: + condition: service_healthy + + db: + image: postgres:16-alpine + environment: + - POSTGRES_USER=prompts + - POSTGRES_PASSWORD=prompts + - POSTGRES_DB=prompts + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U prompts"] + interval: 10s + timeout: 5s + retries: 5 + +volumes: + postgres_data: +``` + +## Data Persistence + +### All-in-One Image + +Data is stored in `/data` inside the container: +- `/data/postgres` - PostgreSQL database files + +Mount a volume to persist data: + +```bash +docker run -d \ + -v prompts-data:/data \ + ghcr.io/f/prompts.chat +``` + +### Backup + +```bash +# Backup database +docker exec prompts pg_dump -U prompts prompts > backup.sql + +# Restore database +docker exec -i prompts psql -U prompts prompts < backup.sql +``` + +## Building Locally + +Build the all-in-one image: + +```bash +docker build -f docker/Dockerfile -t prompts.chat . +docker run -p 80:80 prompts.chat +``` + +Build the app-only image (for docker-compose): + +```bash +docker build -f docker/Dockerfile.app -t prompts.chat-app . +``` + +## Health Check + +The container includes a health check endpoint: + +```bash +curl http://localhost/api/health +``` + +Response: +```json +{ + "status": "healthy", + "timestamp": "2024-01-01T00:00:00.000Z", + "database": "connected" +} +``` + +## Troubleshooting + +### View Logs + +```bash +# All logs +docker logs prompts + +# Follow logs +docker logs -f prompts + +# PostgreSQL logs (inside container) +docker exec prompts cat /var/log/supervisor/postgresql.log + +# Next.js logs (inside container) +docker exec prompts cat /var/log/supervisor/nextjs.log +``` + +### Database Access + +```bash +# Connect to PostgreSQL +docker exec -it prompts psql -U prompts -d prompts + +# Run SQL query +docker exec prompts psql -U prompts -d prompts -c "SELECT COUNT(*) FROM \"Prompt\"" +``` + +### Container Shell + +```bash +docker exec -it prompts bash +``` + +### Common Issues + +**Container won't start:** +- Check logs: `docker logs prompts` +- Ensure port 80 is available: `lsof -i :80` + +**Database connection errors:** +- Wait for PostgreSQL to initialize (can take 30-60 seconds on first run) +- Check database logs: `docker exec prompts cat /var/log/supervisor/postgresql.log` + +**Authentication issues:** +- Ensure `AUTH_SECRET` is set for production +- For OAuth, verify callback URLs are configured correctly + +## Resource Requirements + +Minimum: +- 1 CPU core +- 1GB RAM +- 2GB disk space + +Recommended: +- 2 CPU cores +- 2GB RAM +- 10GB disk space + +## Updating + +```bash +# Pull latest image +docker pull ghcr.io/f/prompts.chat + +# Stop and remove old container +docker stop prompts && docker rm prompts + +# Start new container (data persists in volume) +docker run -d \ + --name prompts \ + -p 80:80 \ + -v prompts-data:/data \ + -e AUTH_SECRET="your-secret-key" \ + ghcr.io/f/prompts.chat +``` + +## Security Considerations + +1. **Always set AUTH_SECRET** in production +2. **Use HTTPS** - put a reverse proxy (nginx, Caddy, Traefik) in front +3. **Limit exposed ports** - only expose what's needed +4. **Regular updates** - pull the latest image regularly +5. **Backup data** - regularly backup the `/data` volume + +## Example: Running Behind Nginx + +```nginx +server { + listen 443 ssl http2; + server_name prompts.example.com; + + ssl_certificate /etc/letsencrypt/live/prompts.example.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/prompts.example.com/privkey.pem; + + location / { + proxy_pass http://localhost:80; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + } +} +``` + +## License + +MIT diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..ee2e1719 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,46 @@ +# Docker Compose for prompts.chat +# Alternative to the all-in-one image - separates app and database +# +# Usage: +# docker compose up -d +# docker compose down + +services: + app: + build: + context: . + dockerfile: docker/Dockerfile.app + ports: + - "80:3000" + environment: + - NODE_ENV=production + - DATABASE_URL=postgresql://prompts:prompts@db:5432/prompts?schema=public + - AUTH_SECRET=${AUTH_SECRET:-change-me-in-production} + depends_on: + db: + condition: service_healthy + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + db: + image: postgres:16-alpine + environment: + - POSTGRES_USER=prompts + - POSTGRES_PASSWORD=prompts + - POSTGRES_DB=prompts + volumes: + - postgres_data:/var/lib/postgresql/data + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U prompts -d prompts"] + interval: 10s + timeout: 5s + retries: 5 + +volumes: + postgres_data: diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..f0842d6c --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,91 @@ +# prompts.chat All-in-One Docker Image +# Contains Node.js + PostgreSQL for single-container deployment +# +# Usage: +# docker run -p 80:80 ghcr.io/f/prompts.chat +# docker run -p 80:80 -e AUTH_SECRET=xxx ghcr.io/f/prompts.chat +# docker run -p 80:80 --name my-prompts ghcr.io/f/prompts.chat + +FROM node:24-bookworm-slim AS builder + +WORKDIR /app + +# Copy package files first for better caching +COPY package.json package-lock.json ./ +COPY prisma ./prisma/ + +# Install all dependencies (including dev for build) +RUN npm ci + +# Copy application files +COPY . . + +# Remove unnecessary files +RUN rm -rf .github .claude packages .git + +# Generate Prisma client and build +ENV NEXT_TELEMETRY_DISABLED=1 +RUN npx prisma generate && npm run build + +# Production image +FROM node:24-bookworm-slim + +# Labels for GitHub Container Registry +LABEL org.opencontainers.image.source="https://github.com/f/awesome-chatgpt-prompts" +LABEL org.opencontainers.image.description="prompts.chat - Self-hosted AI prompt library" +LABEL org.opencontainers.image.licenses="MIT" + +# Environment variables +ENV DEBIAN_FRONTEND=noninteractive +ENV NODE_ENV=production +ENV PORT=80 +ENV HOSTNAME="0.0.0.0" +ENV DATABASE_URL="postgresql://prompts:prompts@localhost:5432/prompts?schema=public" + +# Install PostgreSQL, supervisor, and utilities +RUN apt-get update && apt-get install -y --no-install-recommends \ + postgresql-15 \ + postgresql-contrib-15 \ + supervisor \ + curl \ + openssl \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* \ + && mkdir -p /var/run/postgresql /var/log/supervisor \ + && chown -R postgres:postgres /var/run/postgresql + +WORKDIR /app + +# Copy built application from builder +COPY --from=builder /app/public ./public +COPY --from=builder /app/prisma ./prisma +COPY --from=builder /app/prompts.csv ./prompts.csv +COPY --from=builder /app/messages ./messages +COPY --from=builder /app/prompts.config.ts ./prompts.config.ts +COPY --from=builder /app/package.json ./package.json +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static + +# Copy node_modules for prisma CLI (needed for migrations) +COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma +COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma +COPY --from=builder /app/node_modules/prisma ./node_modules/prisma + +# Copy Docker configuration files +COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# Create data directory for PostgreSQL +RUN mkdir -p /data/postgres && chown -R postgres:postgres /data/postgres + +# Expose port +EXPOSE 80 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=90s --retries=3 \ + CMD curl -f http://localhost:80/api/health || exit 1 + +# Entrypoint +ENTRYPOINT ["/entrypoint.sh"] +CMD [] diff --git a/docker/Dockerfile.app b/docker/Dockerfile.app new file mode 100644 index 00000000..d8e772f3 --- /dev/null +++ b/docker/Dockerfile.app @@ -0,0 +1,68 @@ +# prompts.chat App-Only Dockerfile +# For use with docker-compose (separate database container) + +FROM node:24-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Copy package files +COPY package.json package-lock.json ./ +COPY prisma ./prisma/ + +# Install dependencies +RUN npm ci + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Remove unnecessary files +RUN rm -rf .github .claude packages docker .git + +# Generate Prisma client +RUN npx prisma generate + +# Build Next.js +ENV NEXT_TELEMETRY_DISABLED=1 +RUN npm run build + +# Production image +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +# Install netcat for health checks +RUN apk add --no-cache netcat-openbsd curl + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy built files +COPY --from=builder /app/public ./public +COPY --from=builder /app/prisma ./prisma +COPY --from=builder /app/prompts.csv ./prompts.csv + +# Copy standalone build +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +# Copy entrypoint +COPY docker/entrypoint-app.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +ENTRYPOINT ["/entrypoint.sh"] +CMD ["node", "server.js"] diff --git a/docker/entrypoint-app.sh b/docker/entrypoint-app.sh new file mode 100644 index 00000000..9a179c52 --- /dev/null +++ b/docker/entrypoint-app.sh @@ -0,0 +1,24 @@ +#!/bin/sh +set -e + +echo "🚀 Starting prompts.chat..." + +# Wait for database to be ready +echo "⏳ Waiting for database..." +until nc -z ${DATABASE_HOST:-db} ${DATABASE_PORT:-5432}; do + sleep 1 +done +echo "✅ Database is ready" + +# Run migrations +echo "📦 Running database migrations..." +npx prisma migrate deploy + +# Check if database needs seeding +echo "🌱 Checking database..." +# Skip seed check in entrypoint, let the app handle it + +echo "✨ prompts.chat is starting on port ${PORT:-3000}" + +# Execute the main command +exec "$@" diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 00000000..775e8a4f --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,109 @@ +#!/bin/bash +set -e + +echo "" +echo "╔═══════════════════════════════════════════════════════════════╗" +echo "║ ║" +echo "║ 🚀 prompts.chat - AI Prompt Library ║" +echo "║ ║" +echo "╚═══════════════════════════════════════════════════════════════╝" +echo "" + +# Generate AUTH_SECRET if not provided +if [ -z "$AUTH_SECRET" ]; then + export AUTH_SECRET=$(openssl rand -base64 32) + echo "⚠ AUTH_SECRET not provided, generated random secret" + echo " For production, set AUTH_SECRET environment variable" +fi + +# PostgreSQL paths +PGDATA="/data/postgres" +PGBIN="/usr/lib/postgresql/15/bin" + +# Initialize PostgreSQL data directory if needed +if [ ! -f "$PGDATA/PG_VERSION" ]; then + echo "▶ Initializing PostgreSQL database..." + + # Initialize PostgreSQL + su postgres -c "$PGBIN/initdb -D $PGDATA" + + # Configure PostgreSQL + cat >> "$PGDATA/postgresql.conf" << EOF +listen_addresses = 'localhost' +port = 5432 +max_connections = 100 +shared_buffers = 128MB +EOF + + # Configure authentication + cat > "$PGDATA/pg_hba.conf" << EOF +local all all trust +host all all 127.0.0.1/32 trust +host all all ::1/128 trust +EOF + + # Start PostgreSQL temporarily to create database + su postgres -c "$PGBIN/pg_ctl -D $PGDATA -l /tmp/pg.log start" + sleep 3 + + # Create database and user + su postgres -c "$PGBIN/createuser -s prompts 2>/dev/null" || true + su postgres -c "$PGBIN/createdb -O prompts prompts 2>/dev/null" || true + + # Stop PostgreSQL (supervisor will start it) + su postgres -c "$PGBIN/pg_ctl -D $PGDATA stop" + sleep 2 + + echo "✓ PostgreSQL initialized" +fi + +# Start supervisor (manages PostgreSQL and Next.js) +echo "▶ Starting services..." +/usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf & +SUPERVISOR_PID=$! + +# Wait for PostgreSQL to be ready +echo "▶ Waiting for PostgreSQL..." +for i in $(seq 1 30); do + if $PGBIN/pg_isready -h localhost -p 5432 >/dev/null 2>&1; then + echo "✓ PostgreSQL is ready" + break + fi + if [ $i -eq 30 ]; then + echo "✗ PostgreSQL failed to start" + exit 1 + fi + sleep 1 +done + +# Run database migrations +echo "▶ Running database migrations..." +cd /app +./node_modules/prisma/build/index.js migrate deploy +echo "✓ Migrations complete" + +# Seed database if empty (check if Prompt table exists and has data) +PROMPT_COUNT=$(su postgres -c "$PGBIN/psql -h localhost -U prompts -d prompts -t -c \"SELECT COUNT(*) FROM \\\"Prompt\\\"\"" 2>/dev/null | tr -d ' ' || echo "0") +if [ "$PROMPT_COUNT" = "0" ] || [ -z "$PROMPT_COUNT" ]; then + echo "▶ Seeding database with prompts..." + # Run seed script if available + if [ -f "/app/prisma/seed.ts" ]; then + npx tsx /app/prisma/seed.ts || echo "⚠ Seeding skipped or failed" + fi + echo "✓ Database seeded" +else + echo "✓ Database already has $PROMPT_COUNT prompts" +fi + +echo "" +echo "╔═══════════════════════════════════════════════════════════════╗" +echo "║ ║" +echo "║ ✅ prompts.chat is running! ║" +echo "║ ║" +echo "║ 🌐 Open http://localhost:${PORT:-80} in your browser ║" +echo "║ ║" +echo "╚═══════════════════════════════════════════════════════════════╝" +echo "" + +# Keep container running and forward signals +wait $SUPERVISOR_PID diff --git a/docker/supervisord.conf b/docker/supervisord.conf new file mode 100644 index 00000000..4ccb684b --- /dev/null +++ b/docker/supervisord.conf @@ -0,0 +1,28 @@ +[supervisord] +nodaemon=true +user=root +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid +childlogdir=/var/log/supervisor + +[program:postgresql] +command=/usr/lib/postgresql/15/bin/postgres -D /data/postgres +user=postgres +autostart=true +autorestart=true +priority=10 +stdout_logfile=/var/log/supervisor/postgresql.log +stderr_logfile=/var/log/supervisor/postgresql-error.log + +[program:nextjs] +command=node /app/server.js +directory=/app +user=root +autostart=true +autorestart=true +priority=20 +startsecs=10 +startretries=3 +environment=NODE_ENV="production",PORT="%(ENV_PORT)s",HOSTNAME="0.0.0.0",DATABASE_URL="%(ENV_DATABASE_URL)s",AUTH_SECRET="%(ENV_AUTH_SECRET)s" +stdout_logfile=/var/log/supervisor/nextjs.log +stderr_logfile=/var/log/supervisor/nextjs-error.log diff --git a/next.config.ts b/next.config.ts index df272f89..0e464c08 100644 --- a/next.config.ts +++ b/next.config.ts @@ -14,7 +14,7 @@ const nextConfig: NextConfig = { return config; }, // Enable standalone output for Docker - // output: "standalone", + output: "standalone", // Experimental features experimental: { // Enable server actions diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts new file mode 100644 index 00000000..20d1d623 --- /dev/null +++ b/src/app/api/health/route.ts @@ -0,0 +1,30 @@ +import { NextResponse } from "next/server"; +import { db } from "@/lib/db"; + +export const dynamic = "force-dynamic"; + +export async function GET() { + try { + // Check database connection + await db.$queryRaw`SELECT 1`; + + return NextResponse.json( + { + status: "healthy", + timestamp: new Date().toISOString(), + database: "connected", + }, + { status: 200 } + ); + } catch (error) { + return NextResponse.json( + { + status: "unhealthy", + timestamp: new Date().toISOString(), + database: "disconnected", + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 503 } + ); + } +}