From 43c0cf13595c6b0113d0be6c7bf85fd4588a62a7 Mon Sep 17 00:00:00 2001 From: mikicv Date: Sun, 28 Sep 2025 20:47:23 +0100 Subject: [PATCH] docker image to alpine, reduce size, compile scripts, entrypoint for alpine --- Dockerfile.production | 27 +++++++++---- build-scripts.js | 38 +++++++++++++++++++ deploy.sh | 72 +++++++++++++++-------------------- docker-entrypoint-alpine.sh | 76 +++++++++++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 49 deletions(-) create mode 100644 build-scripts.js create mode 100644 docker-entrypoint-alpine.sh diff --git a/Dockerfile.production b/Dockerfile.production index 5e63216..b582393 100644 --- a/Dockerfile.production +++ b/Dockerfile.production @@ -14,8 +14,8 @@ RUN \ fi # Rebuild the source code only when needed -FROM base AS builder -RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/* +FROM node:22-alpine AS builder +RUN apk add --no-cache python3 make g++ curl WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . @@ -23,12 +23,21 @@ COPY . . # Rebuild better-sqlite3 for Alpine Linux RUN npm rebuild better-sqlite3 +# Build TypeScript database scripts to JavaScript +RUN node build-scripts.js + # Build the application RUN npm run build +# Remove development-only dependencies and dedupe after build +RUN npm prune --omit=dev && npm dedupe --prod \ + && find node_modules -type f \( -name "README" -o -name "README.*" -o -name "CHANGELOG*" -o -name "*.md" -o -name "*.map" \) -delete \ + && find node_modules -type d \( -name "__tests__" -o -name "test" -o -name "tests" -o -name "docs" -o -name "examples" \) -prune -exec rm -rf {} + \ + && npm cache clean --force + # Production image, copy all the files and run next -FROM base AS runner -RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* +FROM node:22-alpine AS runner +RUN apk add --no-cache curl WORKDIR /app ENV NODE_ENV=production @@ -38,17 +47,19 @@ ENV NEXT_TELEMETRY_DISABLED=1 COPY --from=builder /app/.next/standalone ./ COPY --from=builder --chown=root:root /app/.next/static ./.next/static -# Copy full node_modules for database operations +# Copy compiled database scripts instead of TypeScript sources +COPY --from=builder /app/dist ./dist + +# Copy minimal runtime dependencies for database operations COPY --from=builder /app/node_modules ./node_modules -# Copy database setup scripts and package.json -COPY --from=builder /app/scripts ./scripts +# Copy database config and package.json COPY --from=builder /app/lib ./lib COPY --from=builder /app/drizzle.config.ts ./drizzle.config.ts COPY --from=builder /app/package.json ./package.json # Copy entrypoint script -COPY docker-entrypoint.sh /usr/local/bin/ +COPY docker-entrypoint-alpine.sh /usr/local/bin/docker-entrypoint.sh RUN chmod +x /usr/local/bin/docker-entrypoint.sh # Create public directory if it doesn't exist diff --git a/build-scripts.js b/build-scripts.js new file mode 100644 index 0000000..54b7088 --- /dev/null +++ b/build-scripts.js @@ -0,0 +1,38 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +console.log('📦 Building TypeScript database scripts...'); + +// Create dist directory +const distDir = path.join(__dirname, 'dist'); +if (!fs.existsSync(distDir)) { + fs.mkdirSync(distDir, { recursive: true }); +} + +// Bundle scripts with esbuild (available via Next.js) +const scripts = [ + 'scripts/check-database.ts', + 'scripts/setup-database.ts' +]; + +scripts.forEach(script => { + const scriptName = path.basename(script, '.ts'); + const outFile = path.join(distDir, `${scriptName}.js`); + + console.log(` Building ${script} -> ${outFile}`); + + try { + execSync(`npx esbuild ${script} --bundle --platform=node --target=node22 --outfile=${outFile} --external:better-sqlite3`, { + stdio: 'pipe' + }); + console.log(` ✅ ${scriptName}.js built successfully`); + } catch (error) { + console.error(` ❌ Failed to build ${scriptName}:`, error.message); + process.exit(1); + } +}); + +console.log('🎉 All database scripts built successfully!'); \ No newline at end of file diff --git a/deploy.sh b/deploy.sh index 44e0b05..8ec0aac 100755 --- a/deploy.sh +++ b/deploy.sh @@ -14,14 +14,6 @@ if [ ! -f .env.production ]; then exit 1 fi -# Create necessary directories -echo "📁 Creating necessary directories..." -mkdir -p data backups logs - -# Set proper permissions -echo "🔒 Setting directory permissions..." -chmod 755 data backups logs - # Pull latest changes (if using git) if [ -d ".git" ]; then echo "📦 Pulling latest changes..." @@ -29,59 +21,57 @@ if [ -d ".git" ]; then fi # Build and deploy with Docker Compose -echo "� Building and starting Docker containers..." -echo "🐳 Building and starting Docker containers..." +echo "🐳 Building and deploying containers..." # Stop existing containers docker compose -f docker-compose.production.yml down || echo "No existing containers to stop" -# Build and start containers +# Build and start containers (database initialization is automated) docker compose -f docker-compose.production.yml up -d --build -# Wait for containers to be healthy -echo "⏳ Waiting for containers to be healthy..." -sleep 30 - -# Check health -echo "🔍 Checking application health..." -for i in {1..10}; do - if curl -f http://localhost:3036/api/health >/dev/null 2>&1; then +# Wait for health check to pass (container has built-in health checks) +echo "⏳ Waiting for application to be ready..." +timeout=60 +counter=0 +while [ $counter -lt $timeout ]; do + if docker compose -f docker-compose.production.yml ps tt-booking | grep -q "healthy"; then echo "✅ Application is healthy!" break - elif [ $i -eq 10 ]; then - echo "❌ Application health check failed after 10 attempts" + elif [ $counter -eq $((timeout-10)) ]; then + echo "❌ Application failed to become healthy within ${timeout}s" echo "📋 Container logs:" - docker compose -f docker-compose.production.yml logs tt-booking + docker compose -f docker-compose.production.yml logs --tail=30 tt-booking exit 1 else - echo "⏳ Attempt $i/10: Application not ready yet, waiting..." - sleep 10 + echo "⏳ Waiting for health check... (${counter}s/${timeout}s)" + sleep 5 + counter=$((counter+5)) fi done -# Show running containers -echo "📊 Running containers:" -docker compose -f docker-compose.production.yml ps +# Show deployment status +echo "📊 Deployment Status:" +docker compose -f docker-compose.production.yml ps tt-booking -# Show logs echo "📋 Recent application logs:" -docker compose -f docker-compose.production.yml logs --tail=20 tt-booking +docker compose -f docker-compose.production.yml logs --tail=10 tt-booking echo "" echo "🎉 Deployment completed successfully!" echo "" -echo "📊 Application Status:" -echo " • URL: https://lcc-tt-booking.mikicvi.com" -echo " • Health Check: http://localhost:3036/api/health" -echo " • Container Status: $(docker compose -f docker-compose.production.yml ps -q tt-booking | xargs docker inspect -f '{{.State.Status}}')" +echo "📊 Application Details:" +echo " - URL: https://lcc-tt-booking.mikicvi.com" +echo " - Local: http://localhost:3036" +echo " - Health: http://localhost:3036/api/health" echo "" -echo "🔧 Useful commands:" -echo " • View logs: docker compose -f docker-compose.production.yml logs -f tt-booking" -echo " • Restart: docker compose -f docker-compose.production.yml restart tt-booking" -echo " • Stop: docker compose -f docker-compose.production.yml down" +echo "🔧 Management commands:" +echo " - Logs: docker compose -f docker-compose.production.yml logs -f tt-booking" +echo " - Restart: docker compose -f docker-compose.production.yml restart tt-booking" +echo " - Stop: docker compose -f docker-compose.production.yml down" +echo " - Shell: docker compose -f docker-compose.production.yml exec tt-booking sh" echo "" -echo "⚠️ Don't forget to:" -echo " 1. Set up Cloudflare Tunnel to expose your application" -echo " 2. Update your .env.production with real email credentials" -echo " 3. Change the default admin password" +echo "⚠️ Post-deployment checklist:" +echo " - Cloudflare Tunnel is configured and running" +echo " - Admin password changed from default" +echo " - Email settings configured in .env.production" echo "" \ No newline at end of file diff --git a/docker-entrypoint-alpine.sh b/docker-entrypoint-alpine.sh new file mode 100644 index 0000000..0c80114 --- /dev/null +++ b/docker-entrypoint-alpine.sh @@ -0,0 +1,76 @@ +#!/bin/sh +set -e + +echo "🚀 Starting TT Booking Production..." + +# Create required directories if they don't exist +echo "📁 Ensuring directories exist..." +mkdir -p /app/data /app/backups /app/logs +chmod 755 /app/data /app/backups /app/logs + +# Set defaults for environment variables +export DATABASE_URL="${DATABASE_URL:-/app/data/sqlite.db}" +export NODE_ENV="${NODE_ENV:-production}" +export ADMIN_EMAIL="${ADMIN_EMAIL:-admin@tabletennis.com}" +export ADMIN_PASSWORD="${ADMIN_PASSWORD:-admin123}" + +# Validate required environment variables +if [ -z "$NEXTAUTH_SECRET" ]; then + echo "❌ NEXTAUTH_SECRET is required but not set" + echo "💡 Generate one with: openssl rand -base64 32" + exit 1 +fi + +if [ -z "$NEXTAUTH_URL" ]; then + echo "❌ NEXTAUTH_URL is required but not set" + echo "💡 Set to your application URL, e.g., https://your-domain.com" + exit 1 +fi + +# Check database state and determine what actions are needed +echo "📊 Analyzing database state..." +DB_CHECK_EXIT=0 +node dist/check-database.js || DB_CHECK_EXIT=$? + +case $DB_CHECK_EXIT in + 0) + echo "✅ Database is ready - no action needed" + ;; + 1) + echo "🔧 Database needs migration..." + npm run db:push + echo "✅ Migration completed" + ;; + 2) + echo "🌱 Database needs seeding..." + echo " Admin Email: $ADMIN_EMAIL" + echo " Admin Password: [HIDDEN]" + node dist/setup-database.js --essential-only + echo "✅ Seeding completed" + echo "💡 You can now login with: $ADMIN_EMAIL / $ADMIN_PASSWORD" + ;; + 3) + echo "🔄 Database needs migration and seeding..." + npm run db:push + echo "✅ Migration completed" + echo "🌱 Seeding database..." + echo " Admin Email: $ADMIN_EMAIL" + echo " Admin Password: [HIDDEN]" + node dist/setup-database.js --essential-only + echo "✅ Database initialization completed" + echo "💡 You can now login with: $ADMIN_EMAIL / $ADMIN_PASSWORD" + ;; + 4) + echo "❌ Database state check failed - see logs above" + exit 1 + ;; + *) + echo "❌ Unexpected database check result: $DB_CHECK_EXIT" + exit 1 + ;; +esac + +echo "🌟 Starting server..." + +# Execute the main command +exec "$@" \ No newline at end of file