Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -52,15 +51,27 @@ 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

# Expose port
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"]
39 changes: 30 additions & 9 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -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:
13 changes: 13 additions & 0 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -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 "$@"
8 changes: 5 additions & 3 deletions lib/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down