diff --git a/Dockerfile b/Dockerfile index 50055b97..5e3ee8bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,8 +36,7 @@ FROM oven/bun:1.3.5-alpine AS runner WORKDIR /app # Create non-root group and user, with nextjs belonging to nodejs group -RUN addgroup -g 1001 -S nodejs \ - && adduser -S -u 1001 -G nodejs nextjs +RUN addgroup -g 1001 -S nodejs && adduser -S -u 1001 -G nodejs nextjs # Environment variables ENV NODE_ENV=production @@ -52,6 +51,16 @@ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static # Copy public folder COPY --from=builder --chown=nextjs:nodejs /app/public ./public +# Copy migration files and entrypoint +# Note: In standalone mode, we need to ensure all migration dependencies are available. +# We copy the entire project source temporarily to a different folder if needed, +# or just ensure drizzle and lib/db are available for the migration script. +COPY --from=builder --chown=nextjs:nodejs /app/drizzle ./drizzle +COPY --from=builder --chown=nextjs:nodejs /app/lib/db/migrate.ts ./lib/db/migrate.ts +COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json +COPY --chown=nextjs:nodejs docker-entrypoint.sh ./docker-entrypoint.sh +RUN chmod +x docker-entrypoint.sh + # Switch to non-root user USER nextjs @@ -59,8 +68,10 @@ USER nextjs EXPOSE 3000 # Health check (uses Bun to fetch; adjust /api/health if your endpoint differs) -HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ - CMD ["bun", "--eval", "fetch('http://localhost:3000/api/health').then(r => r.ok ? process.exit(0) : process.exit(1)).catch(() => process.exit(1))"] +HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 CMD ["bun", "--eval", "fetch('http://localhost:3000/api/health').then(r => r.ok ? process.exit(0) : process.exit(1)).catch(() => process.exit(1))"] + +# Use entrypoint script to handle migrations +ENTRYPOINT ["./docker-entrypoint.sh"] # Run the standalone server with Bun (fully compatible in Bun 1.3+) CMD ["bun", "server.js"] diff --git a/docker-compose.yaml b/docker-compose.yaml index bf42d54a..48ffb53f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,49 +1,70 @@ name: qcx-stack services: + db: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_USER: qcxuser + POSTGRES_PASSWORD: qcxpassword + POSTGRES_DB: qcxdb + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U qcxuser -d qcxdb"] + interval: 10s + timeout: 5s + retries: 5 + qcx: build: context: . dockerfile: Dockerfile - target: runner # Use the production stage + target: runner ports: - "3000:3000" environment: - NODE_ENV=production - PORT=3000 - HOSTNAME=0.0.0.0 - env_file: - - .env.local # Load environment variables from .env.local if it exists + - DATABASE_URL=postgres://qcxuser:qcxpassword@db:5432/qcxdb + - EXECUTE_MIGRATIONS=true + depends_on: + db: + condition: service_healthy restart: unless-stopped healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] + test: ["CMD", "bun", "--eval", "fetch('http://localhost:3000/api/health').then(r => r.ok ? process.exit(0) : process.exit(1)).catch(() => process.exit(1))"] interval: 30s timeout: 10s retries: 3 start_period: 40s - # Development service (optional - for local development with hot reload) + # Development service qcx-dev: build: context: . dockerfile: Dockerfile - target: builder # Use the builder stage for development + target: builder command: bun dev ports: - "3001:3000" environment: - NODE_ENV=development - PORT=3000 - env_file: - - .env.local + - DATABASE_URL=postgres://qcxuser:qcxpassword@db:5432/qcxdb + depends_on: + db: + condition: service_healthy volumes: - .:/app - node_modules:/app/node_modules - next_build:/app/.next restart: unless-stopped profiles: - - dev # Only start this service when explicitly requested with --profile dev + - dev volumes: + postgres_data: node_modules: next_build: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 00000000..5eef74c4 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -e + +# Run migrations if enabled +if [ "$EXECUTE_MIGRATIONS" = "true" ]; then + echo "Running database migrations..." + # Use the project-level bun to run migrations if available, otherwise assume global + bun run db:migrate || echo "Migration failed, but continuing..." +fi + +# Execute the main command +echo "Starting application..." +exec "$@" diff --git a/lib/db/index.ts b/lib/db/index.ts index 0283d9a3..f0b43eb7 100644 --- a/lib/db/index.ts +++ b/lib/db/index.ts @@ -5,12 +5,14 @@ import * as schema from './schema'; dotenv.config({ path: '.env.local' }); -if (!process.env.DATABASE_URL) { - throw new Error('DATABASE_URL environment variable is not set for Drizzle client'); +// In production/build environments, we might not have DATABASE_URL immediately available +// especially during Next.js static optimization phases. +if (!process.env.DATABASE_URL && process.env.NODE_ENV === 'production') { + console.warn('DATABASE_URL environment variable is not set. Database features will be unavailable.'); } const poolConfig: PoolConfig = { - connectionString: process.env.DATABASE_URL, + connectionString: process.env.DATABASE_URL || 'postgres://localhost:5432/postgres', }; // Conditionally apply SSL for Supabase URLs