diff --git a/.dockerignore b/.dockerignore index 3bd0b97a..d68738d3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,6 +10,7 @@ dist # Git .git .gitignore +.gitattributes # IDE .vscode @@ -23,10 +24,11 @@ dist .env.development.local .env.test.local .env.production.local +.env.sentry-build-plugin # Docker +compose.yml docker-compose*.yml -Dockerfile* # Development files .github @@ -38,6 +40,8 @@ packages # Test files coverage .nyc_output +vitest.config.ts +vitest.setup.ts # OS files .DS_Store diff --git a/DOCKER.md b/DOCKER.md index b7f4ef75..0a1d10fc 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -1,45 +1,68 @@ # Docker Deployment Guide -Run your own prompts.chat instance with a single command. +Run your own prompts.chat instance using Docker Compose. ## Quick Start ```bash -docker run -d \ - --name prompts \ - -p 4444:3000 \ - -v prompts-data:/data \ - ghcr.io/f/prompts.chat +git clone https://github.com/f/prompts.chat.git +cd prompts.chat +docker compose up -d ``` -**First run:** The container will clone the repository and build the app (~3-5 minutes). -**Subsequent runs:** Starts immediately using the cached build. - Open http://localhost:4444 in your browser. -## Custom Branding +## Using a Pre-built Image -Customize your instance with environment variables: +Edit `compose.yml` and replace the `build` block with the published image: -```bash -docker run -d \ - --name my-prompts \ - -p 4444:3000 \ - -v prompts-data:/data \ - -e PCHAT_NAME="Acme Prompts" \ - -e PCHAT_DESCRIPTION="Our team's AI prompt library" \ - -e PCHAT_COLOR="#ff6600" \ - -e PCHAT_AUTH_PROVIDERS="github,google" \ - -e PCHAT_LOCALES="en,es,fr" \ - ghcr.io/f/prompts.chat +```yaml +services: + app: + # build: + # context: . + # dockerfile: docker/Dockerfile + image: ghcr.io/f/prompts.chat:latest ``` -> **Note:** Branding is applied during the first build. To change branding later, delete the volume and re-run: -> ```bash -> docker rm -f my-prompts -> docker volume rm prompts-data -> docker run ... # with new env vars -> ``` +Then run: + +```bash +docker compose up -d +``` + +## Standalone (Bring Your Own Database) + +If you already have a PostgreSQL instance, you can run just the app container: + +```bash +docker build -f docker/Dockerfile -t prompts.chat . +docker run -d \ + --name prompts \ + -p 4444:3000 \ + -e DATABASE_URL="postgresql://user:pass@your-db-host:5432/prompts?schema=public" \ + -e AUTH_SECRET="$(openssl rand -base64 32)" \ + prompts.chat +``` + +## Custom Branding + +All branding is configured via `PCHAT_*` environment variables at runtime -- no rebuild needed. + +```yaml +# compose.yml +services: + app: + environment: + # ... existing vars ... + PCHAT_NAME: "Acme Prompts" + PCHAT_DESCRIPTION: "Our team's AI prompt library" + PCHAT_COLOR: "#ff6600" + PCHAT_AUTH_PROVIDERS: "github,google" + PCHAT_LOCALES: "en,es,fr" +``` + +Then restart: `docker compose up -d` ## Configuration Variables @@ -95,103 +118,102 @@ All variables are prefixed with `PCHAT_` to avoid conflicts. | Variable | Description | Default | |----------|-------------|---------| -| `AUTH_SECRET` | Secret for authentication tokens | Auto-generated | -| `PORT` | Internal container port | `3000` | -| `DATABASE_URL` | PostgreSQL connection string | Internal DB | +| `AUTH_SECRET` | Secret for authentication tokens | Auto-generated (set explicitly for production) | +| `DATABASE_URL` | PostgreSQL connection string | Set in compose.yml | +| `DIRECT_URL` | Direct PostgreSQL URL (bypasses poolers) | Same as DATABASE_URL | +| `PORT` | Host port mapping | `4444` | ## Production Setup -For production, set `AUTH_SECRET` explicitly: +For production, always set `AUTH_SECRET` explicitly: ```bash -docker run -d \ - --name prompts \ - -p 4444:3000 \ - -v prompts-data:/data \ - -e AUTH_SECRET="$(openssl rand -base64 32)" \ - -e PCHAT_NAME="My Company Prompts" \ - ghcr.io/f/prompts.chat +# Generate a secret +export AUTH_SECRET=$(openssl rand -base64 32) + +# Start with explicit secret +docker compose up -d +``` + +Or add it to a `.env` file next to `compose.yml`: + +```env +AUTH_SECRET=your-secret-key-here ``` ### With OAuth Providers -```bash -docker run -d \ - --name prompts \ - -p 4444:3000 \ - -v prompts-data:/data \ - -e AUTH_SECRET="your-secret-key" \ - -e PCHAT_AUTH_PROVIDERS="github,google" \ - -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 +```yaml +# compose.yml +services: + app: + environment: + # ... existing vars ... + PCHAT_AUTH_PROVIDERS: "github,google" + AUTH_GITHUB_ID: "your-github-client-id" + AUTH_GITHUB_SECRET: "your-github-client-secret" + AUTH_GOOGLE_ID: "your-google-client-id" + AUTH_GOOGLE_SECRET: "your-google-client-secret" ``` ### With AI Features (OpenAI) +```yaml +# compose.yml +services: + app: + environment: + # ... existing vars ... + PCHAT_FEATURE_AI_SEARCH: "true" + OPENAI_API_KEY: "sk-..." +``` + +## Database Seeding + +Seed the database with example prompts: + ```bash -docker run -d \ - --name prompts \ - -p 4444:3000 \ - -v prompts-data:/data \ - -e PCHAT_FEATURE_AI_SEARCH="true" \ - -e OPENAI_API_KEY="sk-..." \ - ghcr.io/f/prompts.chat +docker compose exec app npx prisma db seed ``` ## Custom Logo -Mount your logo file: +Mount your logo file into the app container: -```bash -docker run -d \ - --name prompts \ - -p 4444:3000 \ - -v prompts-data:/data \ - -v ./my-logo.svg:/data/app/public/logo.svg \ - -e PCHAT_NAME="My App" \ - ghcr.io/f/prompts.chat +```yaml +# compose.yml +services: + app: + volumes: + - ./my-logo.svg:/app/public/logo.svg + environment: + PCHAT_LOGO: "/logo.svg" ``` ## 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 -``` +PostgreSQL data is stored in the `postgres_data` named volume and persists across container restarts, rebuilds, and image updates. ### Backup ```bash # Backup database -docker exec prompts pg_dump -U prompts prompts > backup.sql +docker compose exec db pg_dump -U prompts prompts > backup.sql # Restore database -docker exec -i prompts psql -U prompts prompts < backup.sql +docker compose exec -T db psql -U prompts prompts < backup.sql ``` ## Building Locally -Build and run locally: - ```bash -docker build -f docker/Dockerfile -t prompts.chat . -docker run -p 4444:3000 -v prompts-data:/data prompts.chat +docker compose build +docker compose up -d ``` ## Health Check -The container includes a health check endpoint: +The app container includes a health check endpoint: ```bash curl http://localhost:4444/api/health @@ -211,48 +233,82 @@ Response: ### View Logs ```bash -# All logs -docker logs prompts +# All services +docker compose logs # Follow logs -docker logs -f prompts +docker compose logs -f -# PostgreSQL logs (inside container) -docker exec prompts cat /var/log/supervisor/postgresql.log +# App logs only +docker compose logs app -# Next.js logs (inside container) -docker exec prompts cat /var/log/supervisor/nextjs.log +# Database logs only +docker compose logs db ``` ### Database Access ```bash # Connect to PostgreSQL -docker exec -it prompts psql -U prompts -d prompts +docker compose exec db psql -U prompts -d prompts -# Run SQL query -docker exec prompts psql -U prompts -d prompts -c "SELECT COUNT(*) FROM \"Prompt\"" +# Run a query +docker compose exec db psql -U prompts -d prompts -c "SELECT COUNT(*) FROM \"Prompt\"" ``` ### Container Shell ```bash -docker exec -it prompts bash +docker compose exec app sh +docker compose exec db bash ``` ### Common Issues -**Container won't start:** -- Check logs: `docker logs prompts` -- Ensure port 4444 is available: `lsof -i :4444` +**App container keeps restarting:** +- Check logs: `docker compose logs app` +- Database may not be ready yet -- the entrypoint retries for up to 60 seconds **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` +- Verify the `db` service is healthy: `docker compose ps` +- Check database logs: `docker compose logs db` **Authentication issues:** -- Ensure `AUTH_SECRET` is set for production -- For OAuth, verify callback URLs are configured correctly +- Set `AUTH_SECRET` explicitly for production +- For OAuth, verify callback URLs match your domain + +## Updating + +```bash +# If using pre-built images +docker compose pull +docker compose up -d + +# If building locally +git pull +docker compose build +docker compose up -d +``` + +Data persists in the `postgres_data` volume across updates. + +## Migrating from the Old Single-Image Setup + +If you were using the previous all-in-one Docker image: + +```bash +# 1. Export your database from the old container +docker exec prompts pg_dump -U prompts prompts > backup.sql + +# 2. Stop and remove the old container +docker stop prompts && docker rm prompts + +# 3. Start the new compose setup +docker compose up -d + +# 4. Import your data +docker compose exec -T db psql -U prompts prompts < backup.sql +``` ## Resource Requirements @@ -266,33 +322,9 @@ Recommended: - 2GB RAM - 10GB disk space -## Updating +## Running Behind a Reverse Proxy -```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 4444:3000 \ - -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 ```nginx server { @@ -316,6 +348,23 @@ server { } ``` +### Caddy + +``` +prompts.example.com { + reverse_proxy localhost:4444 +} +``` + +## Security Considerations + +1. **Always set AUTH_SECRET** in production +2. **Use HTTPS** -- put a reverse proxy (Nginx, Caddy, Traefik) in front +3. **Change default database password** -- update `POSTGRES_PASSWORD` in compose.yml and the connection strings +4. **Limit exposed ports** -- only expose what's needed +5. **Regular updates** -- pull the latest image regularly +6. **Backup data** -- regularly backup the database + ## License MIT diff --git a/PR_DESCRIPTION.md b/PR_DESCRIPTION.md new file mode 100644 index 00000000..44bbcec1 --- /dev/null +++ b/PR_DESCRIPTION.md @@ -0,0 +1,57 @@ +# Fix Docker setup: proper multi-container architecture with Docker Compose + +## Summary + +The previous Docker setup bundled PostgreSQL and the Next.js app into a single image managed by `supervisord`. This is an anti-pattern — it violates the "one container, one process" principle, clones the repo and runs the full build at runtime (3–5 min cold start), and makes scaling, updating, and debugging significantly harder. + +This PR replaces it with a proper Docker Compose setup following the "Docker way": + +- **`app`** — Next.js application container (multi-stage build, standalone output) +- **`db`** — Official `postgres:17-bookworm` container + +Database data is persisted via a named Docker volume and survives container restarts, rebuilds, and image updates. + +## Changes + +### New files +- **`compose.yml`** — Docker Compose configuration with `app` and `db` services, healthchecks, and a named volume for PostgreSQL data +- **`docker/entrypoint.sh`** — Lightweight entrypoint: waits for the database, runs `prisma migrate deploy`, then starts the app + +### Modified files +- **`docker/Dockerfile`** — Replaced the single-stage bootstrap image with a proper 4-stage multi-stage build: + - `base` — Shared `node:24-bookworm-slim` with system dependencies + - `deps` — Runs `npm ci` (and `prisma generate` via postinstall) + - `builder` — Runs `npm run build`, produces `.next/standalone/` + - `runner` — Minimal production image; copies standalone output, installs only Prisma CLI for migrations +- **`.dockerignore`** — Added `compose.yml`, test config files +- **`DOCKER.md`** — Complete rewrite covering the new compose-based workflow, standalone single-container usage, migration guide from the old setup, backup/restore, and production deployment tips + +### Deleted files +- `docker/bootstrap.sh` — Replaced by `docker/entrypoint.sh` +- `docker/supervisord.conf` — No longer needed (one process per container) + +## Key design decisions + +- **PCHAT_* env vars work at runtime** — `src/lib/config/index.ts` already applies overrides via `applyEnvOverrides()`. No rebuild is needed to change branding or feature flags; just update environment variables and restart. +- **Migrations run at startup** — `prisma migrate deploy` runs in the entrypoint before the app starts, keeping deployments simple without init containers. +- **Seeding is manual** — `docker compose exec app npx prisma db seed` +- **All images are Debian-based** — `node:24-bookworm-slim` (bookworm) and `postgres:17-bookworm` +- **`.github/workflows/docker-publish.yml` needs no changes** — it already builds `./docker/Dockerfile` with context `.` + +## Quick start after this PR + +```bash +git clone https://github.com/f/prompts.chat.git +cd prompts.chat +docker compose up -d +``` + +Open http://localhost:4444 + +## Testing + +- `docker compose build` — builds successfully +- `docker compose up -d` — both services start, DB healthcheck passes before app starts +- `curl http://localhost:4444/api/health` — returns `{"status":"healthy","database":"connected"}` +- `curl http://localhost:4444/` — homepage returns 200 +- Data persists across `docker compose down && docker compose up -d` diff --git a/compose.yml b/compose.yml new file mode 100644 index 00000000..162c1e92 --- /dev/null +++ b/compose.yml @@ -0,0 +1,44 @@ +services: + db: + image: postgres:17-bookworm + restart: unless-stopped + 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 -d prompts"] + interval: 5s + timeout: 5s + retries: 10 + start_period: 10s + + app: + build: + context: . + dockerfile: docker/Dockerfile + # To use a pre-built image instead of building locally, comment out + # the 'build' block above and uncomment the line below: + # image: ghcr.io/f/prompts.chat:latest + restart: unless-stopped + ports: + - "${PORT:-4444}:3000" + environment: + DATABASE_URL: "postgresql://prompts:prompts@db:5432/prompts?schema=public" + DIRECT_URL: "postgresql://prompts:prompts@db:5432/prompts?schema=public" + AUTH_SECRET: "${AUTH_SECRET:-}" + NODE_ENV: production + # Branding (all optional — set any PCHAT_* variable to customize at runtime) + # PCHAT_NAME: "My Prompt Library" + # PCHAT_DESCRIPTION: "Our team's AI prompt collection" + # PCHAT_COLOR: "#6366f1" + # PCHAT_AUTH_PROVIDERS: "credentials" + # PCHAT_LOCALES: "en" + depends_on: + db: + condition: service_healthy + +volumes: + postgres_data: diff --git a/docker/Dockerfile b/docker/Dockerfile index 693398a9..8e4ce856 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,49 +1,94 @@ -# prompts.chat Bootstrap Docker Image -# Lightweight image that clones and builds on first run +# prompts.chat Docker Image +# Multi-stage build for a production-ready Next.js application # -# Usage: -# docker run -p 4444:3000 -v prompts-data:/data ghcr.io/f/prompts.chat -# docker run -p 4444:3000 -v prompts-data:/data -e PCHAT_NAME="My App" ghcr.io/f/prompts.chat +# Usage with Docker Compose: +# docker compose up -d +# +# Usage standalone (bring your own PostgreSQL): +# docker build -f docker/Dockerfile -t prompts.chat . +# docker run -p 4444:3000 -e DATABASE_URL="postgresql://..." prompts.chat -FROM node:24-bookworm-slim +# ---- Stage 1: Base ---- +FROM node:24-bookworm-slim AS base + +RUN apt-get update && apt-get install -y --no-install-recommends \ + openssl \ + curl \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# ---- Stage 2: Dependencies ---- +FROM base AS deps + +# Copy dependency manifests and Prisma schema (postinstall runs prisma generate) +COPY package.json package-lock.json ./ +COPY prisma/schema.prisma prisma/schema.prisma +COPY prisma.config.ts tsconfig.json ./ + +# Dummy DATABASE_URL for prisma generate (no actual connection is made) +ENV DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy" + +RUN npm ci + +# ---- Stage 3: Builder ---- +FROM base AS builder + +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +ENV NEXT_TELEMETRY_DISABLED=1 +ENV DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy" + +RUN npm run build + +# ---- Stage 4: Runner ---- +FROM node:24-bookworm-slim AS runner LABEL org.opencontainers.image.source="https://github.com/f/prompts.chat" LABEL org.opencontainers.image.description="prompts.chat - Self-hosted AI prompt library" LABEL org.opencontainers.image.licenses="MIT" -# Install PostgreSQL, git, and utilities RUN apt-get update && apt-get install -y --no-install-recommends \ - postgresql-15 \ - postgresql-contrib-15 \ - supervisor \ - git \ - curl \ openssl \ + curl \ ca-certificates \ - && rm -rf /var/lib/apt/lists/* \ - && mkdir -p /var/run/postgresql /var/log/supervisor \ - && chown -R postgres:postgres /var/run/postgresql + && rm -rf /var/lib/apt/lists/* -# Create directories WORKDIR /app -RUN mkdir -p /data/postgres /data/app && chown -R postgres:postgres /data/postgres -# Copy bootstrap scripts -COPY docker/bootstrap.sh /bootstrap.sh -COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf -RUN chmod +x /bootstrap.sh +RUN groupadd --system --gid 1001 nodejs && \ + useradd --system --uid 1001 --gid nodejs nextjs + +# Copy standalone build output +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +COPY --from=builder --chown=nextjs:nodejs /app/public ./public + +# Copy Prisma schema, migrations, and config for runtime migrations +COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma +COPY --from=builder --chown=nextjs:nodejs /app/prisma.config.ts ./prisma.config.ts +COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json +COPY --from=builder --chown=nextjs:nodejs /app/tsconfig.json ./tsconfig.json + +# Install only Prisma CLI and its dependencies for runtime migrations +RUN npm install --no-save prisma@6.19 dotenv + +# Copy entrypoint script +COPY --chown=nextjs:nodejs docker/entrypoint.sh ./entrypoint.sh +RUN chmod +x ./entrypoint.sh -# Environment defaults ENV NODE_ENV=production ENV PORT=3000 -ENV HOSTNAME="0.0.0.0" -ENV DATABASE_URL="postgresql://prompts:prompts@localhost:5432/prompts?schema=public" -ENV DIRECT_URL="postgresql://prompts:prompts@localhost:5432/prompts?schema=public" -ENV REPO_URL="https://github.com/f/prompts.chat.git" +ENV HOSTNAME=0.0.0.0 +ENV NEXT_TELEMETRY_DISABLED=1 EXPOSE 3000 -HEALTHCHECK --interval=30s --timeout=10s --start-period=180s --retries=3 \ +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ CMD curl -f http://localhost:3000/api/health || exit 1 -ENTRYPOINT ["/bootstrap.sh"] +USER nextjs + +ENTRYPOINT ["./entrypoint.sh"] diff --git a/docker/bootstrap.sh b/docker/bootstrap.sh deleted file mode 100644 index 9e5996e7..00000000 --- a/docker/bootstrap.sh +++ /dev/null @@ -1,181 +0,0 @@ -#!/bin/bash -set -e - -echo "" -echo "╔═══════════════════════════════════════════════════════════════╗" -echo "║ ║" -echo "║ 🚀 prompts.chat - AI Prompt Library ║" -echo "║ ║" -echo "╚═══════════════════════════════════════════════════════════════╝" -echo "" - -# Paths -APP_DIR="/data/app" -PGDATA="/data/postgres" -PGBIN="/usr/lib/postgresql/15/bin" -BUILD_MARKER="/data/.built" - -# Generate AUTH_SECRET if not provided -if [ -z "$AUTH_SECRET" ]; then - if [ -f "/data/.auth_secret" ]; then - export AUTH_SECRET=$(cat /data/.auth_secret) - else - export AUTH_SECRET=$(openssl rand -base64 32) - echo "$AUTH_SECRET" > /data/.auth_secret - echo "⚠ AUTH_SECRET generated and saved" - fi -fi - -# Initialize PostgreSQL if needed -if [ ! -f "$PGDATA/PG_VERSION" ]; then - echo "▶ Initializing PostgreSQL..." - su postgres -c "$PGBIN/initdb -D $PGDATA" - - cat >> "$PGDATA/postgresql.conf" << EOF -listen_addresses = 'localhost' -port = 5432 -max_connections = 100 -shared_buffers = 128MB -EOF - - 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 - - su postgres -c "$PGBIN/pg_ctl -D $PGDATA -l /tmp/pg.log start" - sleep 3 - su postgres -c "$PGBIN/createuser -s prompts 2>/dev/null" || true - su postgres -c "$PGBIN/createdb -O prompts prompts 2>/dev/null" || true - su postgres -c "$PGBIN/pg_ctl -D $PGDATA stop" - sleep 2 - echo "✓ PostgreSQL initialized" -fi - -# Clone and build on first run -if [ ! -f "$BUILD_MARKER" ]; then - echo "" - echo "▶ First run detected - building prompts.chat..." - echo "" - - # Clone repository - if [ ! -d "$APP_DIR/.git" ]; then - echo "▶ Cloning repository..." - rm -rf "$APP_DIR" - git clone --depth 1 "$REPO_URL" "$APP_DIR" - echo "✓ Repository cloned" - fi - - cd "$APP_DIR" - - # Clean up unnecessary files - rm -rf .github .claude packages .git - - # Install dependencies (including devDependencies needed for build) - echo "▶ Installing dependencies..." - NODE_ENV=development npm ci - echo "✓ Dependencies installed" - - # Run docker-setup.js to generate config with branding - echo "▶ Generating configuration..." - node scripts/docker-setup.js - echo "✓ Configuration generated" - - # Generate Prisma client - echo "▶ Generating Prisma client..." - npx prisma generate - echo "✓ Prisma client generated" - - # Build Next.js - echo "▶ Building Next.js application (this may take a few minutes)..." - npm run build - echo "✓ Build complete" - - # Copy static files for standalone mode - echo "▶ Copying static assets..." - cp -r .next/static .next/standalone/.next/ - cp -r public .next/standalone/ - echo "✓ Static assets copied" - - # Mark as built - touch "$BUILD_MARKER" - - echo "" - echo "✅ Build complete! Starting application..." - echo "" -else - echo "✓ Using existing build" - cd "$APP_DIR" -fi - -# Start supervisord (manages PostgreSQL and Next.js) -echo "▶ Starting services..." -/usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf & -SUPERVISOR_PID=$! - -# Wait for PostgreSQL -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 migrations -echo "▶ Running database migrations..." -cd "$APP_DIR" -npx prisma migrate deploy -echo "✓ Migrations complete" - -# Seed on first run only -SEED_MARKER="/data/.seeded" -if [ ! -f "$SEED_MARKER" ]; then - echo "▶ Seeding database..." - if npx tsx prisma/seed.ts 2>/dev/null; then - touch "$SEED_MARKER" - echo "✓ Database seeded" - else - echo "⚠ Seeding skipped" - fi -fi - -# Wait for supervisord to be ready -echo "▶ Waiting for supervisord..." -for i in $(seq 1 30); do - if kill -0 $SUPERVISOR_PID 2>/dev/null && [ -S /var/run/supervisor.sock ]; then - echo "✓ Supervisord is ready" - break - fi - if ! kill -0 $SUPERVISOR_PID 2>/dev/null; then - echo "✗ Supervisord process died" - exit 1 - fi - if [ $i -eq 30 ]; then - echo "✗ Supervisord socket not available after 30s" - exit 1 - fi - sleep 1 -done - -# Start Next.js via supervisorctl -echo "▶ Starting Next.js..." -supervisorctl start nextjs - -echo "" -echo "╔═══════════════════════════════════════════════════════════════╗" -echo "║ ║" -echo "║ ✅ prompts.chat is running! ║" -echo "║ ║" -echo "║ 🌐 Open http://localhost:${PORT:-80} in your browser ║" -echo "║ ║" -echo "╚═══════════════════════════════════════════════════════════════╝" -echo "" - -wait $SUPERVISOR_PID diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 00000000..21a39541 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,39 @@ +#!/bin/sh +set -e + +# Generate AUTH_SECRET if not provided +if [ -z "$AUTH_SECRET" ]; then + AUTH_SECRET=$(openssl rand -base64 32) + export AUTH_SECRET + echo "[prompts.chat] AUTH_SECRET not set -- generated a random value." + echo "[prompts.chat] Set AUTH_SECRET explicitly for production to persist sessions across restarts." +fi + +# Wait for PostgreSQL to be ready +echo "[prompts.chat] Waiting for database..." +MAX_RETRIES=30 +RETRY_COUNT=0 +until node -e " + const net = require('net'); + const url = new URL(process.env.DATABASE_URL); + const sock = net.createConnection({ host: url.hostname, port: url.port || 5432 }); + sock.on('connect', () => { sock.destroy(); process.exit(0); }); + sock.on('error', () => process.exit(1)); +" 2>/dev/null; do + RETRY_COUNT=$((RETRY_COUNT + 1)) + if [ "$RETRY_COUNT" -ge "$MAX_RETRIES" ]; then + echo "[prompts.chat] ERROR: Database not reachable after ${MAX_RETRIES} attempts." + exit 1 + fi + echo "[prompts.chat] Database not ready (attempt ${RETRY_COUNT}/${MAX_RETRIES}), retrying in 2s..." + sleep 2 +done + +# Run database migrations +echo "[prompts.chat] Running database migrations..." +npx prisma migrate deploy +echo "[prompts.chat] Migrations applied successfully." + +# Start the application +echo "[prompts.chat] Starting application on port ${PORT:-3000}..." +exec node server.js diff --git a/docker/supervisord.conf b/docker/supervisord.conf deleted file mode 100644 index deab710b..00000000 --- a/docker/supervisord.conf +++ /dev/null @@ -1,38 +0,0 @@ -[unix_http_server] -file=/var/run/supervisor.sock -chmod=0700 - -[supervisorctl] -serverurl=unix:///var/run/supervisor.sock - -[rpcinterface:supervisor] -supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface - -[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 /data/app/.next/standalone/server.js -directory=/data/app -user=root -autostart=false -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