From f26ef53c8cfbc0f0109f7bbf8936d3fa2c0d20df Mon Sep 17 00:00:00 2001 From: Dries Augustyns Date: Mon, 29 Dec 2025 14:23:21 +0100 Subject: [PATCH] chore: Improve startup speed of containers --- Dockerfile | 14 ++++ docker-entrypoint-nginx.sh | 106 ++------------------------- docker/generate-url-manifest.sh | 46 ++++++++++++ docker/replace-urls-optimized.sh | 118 +++++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+), 99 deletions(-) create mode 100644 docker/generate-url-manifest.sh create mode 100644 docker/replace-urls-optimized.sh diff --git a/Dockerfile b/Dockerfile index d071a6d..b5ce8c6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -123,6 +123,10 @@ COPY --from=deps /app/yarn.lock ./ # Copy root config files needed for Turbo COPY turbo.json ./ +# Copy manifest generation script +COPY docker/generate-url-manifest.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/generate-url-manifest.sh + # Step 1: Copy and build shared packages (these change less frequently) # Shared packages are dependencies for apps, so build them first COPY packages ./packages @@ -173,6 +177,8 @@ RUN --mount=type=cache,target=/app/.turbo,sharing=locked \ yarn turbo build --filter=wiki # Generate sitemap for wiki RUN NEXT_PUBLIC_WIKI_URI=${WIKI_URI} yarn workspace wiki sitemap +# Generate URL replacement manifest for wiki (build-time optimization) +RUN generate-url-manifest.sh wiki /app/apps/wiki # Step 4: Copy and build Web dashboard COPY apps/web ./apps/web @@ -188,6 +194,8 @@ RUN --mount=type=cache,target=/app/.turbo,sharing=locked \ yarn turbo build --filter=web # Generate sitemap for web RUN NEXT_PUBLIC_DASHBOARD_URI=${DASHBOARD_URI} yarn workspace web sitemap +# Generate URL replacement manifest for web (build-time optimization) +RUN generate-url-manifest.sh web /app/apps/web # Step 5: Copy and build Landing page COPY apps/landing ./apps/landing @@ -203,6 +211,8 @@ RUN --mount=type=cache,target=/app/.turbo,sharing=locked \ yarn turbo build --filter=landing # Generate sitemap for landing RUN NEXT_PUBLIC_LANDING_URI=${LANDING_URI} yarn workspace landing sitemap +# Generate URL replacement manifest for landing (build-time optimization) +RUN generate-url-manifest.sh landing /app/apps/landing # Copy any remaining root files (if needed) COPY . . @@ -317,6 +327,10 @@ COPY --from=builder --chown=plunk:nodejs /app/apps/wiki/openapi.local.json ./app COPY --chown=plunk:nodejs docker/nginx/ /app/docker/nginx/ RUN chmod +x /app/docker/nginx/setup-nginx.sh +# Copy optimized URL replacement script +COPY --chown=plunk:nodejs docker/replace-urls-optimized.sh /app/docker/ +RUN chmod +x /app/docker/replace-urls-optimized.sh + # Copy entrypoint script COPY --chown=plunk:nodejs docker-entrypoint-nginx.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/docker-entrypoint-nginx.sh diff --git a/docker-entrypoint-nginx.sh b/docker-entrypoint-nginx.sh index 71c98e5..28bce12 100644 --- a/docker-entrypoint-nginx.sh +++ b/docker-entrypoint-nginx.sh @@ -16,8 +16,9 @@ fi # Run database migrations echo "πŸ—„οΈ Running database migrations..." -cd /app -if ! yarn workspace @plunk/db migrate:prod; then +# Call Prisma binary directly for fastest startup (avoids Yarn workspace resolution and npx overhead) +# The Prisma client and schema are already available from the build +if ! /app/node_modules/.bin/prisma migrate deploy --schema=/app/packages/db/prisma/schema.prisma; then echo "" echo "❌ ERROR: Database migration failed!" echo " The container cannot start with failed migrations." @@ -71,104 +72,11 @@ echo " LANDING_DOMAIN=www.example.com" echo " WIKI_DOMAIN=docs.example.com" echo " USE_HTTPS=true" -# Replace URLs in built files with runtime configuration -# This approach is simpler and more reliable than window.__ENV__ -replace_urls_in_app() { - local app=$1 - local app_dir=$2 +# Source the optimized URL replacement script +. /app/docker/replace-urls-optimized.sh - echo "πŸ“ Replacing URLs in $app build files..." - - # Define placeholder URLs (built into the app at compile time) - local PLACEHOLDER_API="https://api.useplunk.com" - local PLACEHOLDER_DASHBOARD="https://app.useplunk.com" - local PLACEHOLDER_LANDING="https://www.useplunk.com" - local PLACEHOLDER_WIKI="https://docs.useplunk.com" - - # Find and replace URLs in all built files - # Process .next directory (contains all built output: static/, server/, standalone bundles) - # Include .js, .json, .html files to catch all possible occurrences - echo " πŸ” Scanning for files with placeholder URLs..." - - # Create a temporary file list of files that need processing - local temp_file_list="/tmp/replace_urls_${app}.txt" - - # Search in .next directory for all file types that might contain URLs - # Include: .js (bundles), .json (manifests), .html (static pages), .rsc (React Server Components) - find "$app_dir/.next" -type f \( -name "*.js" -o -name "*.json" -o -name "*.html" -o -name "*.rsc" \) \ - -exec grep -l -E "$PLACEHOLDER_API|$PLACEHOLDER_DASHBOARD|$PLACEHOLDER_LANDING|$PLACEHOLDER_WIKI" {} \; \ - 2>/dev/null > "$temp_file_list" || true - - # Count and process files - local file_count=$(wc -l < "$temp_file_list" | tr -d ' ') - echo " πŸ“Š Found $file_count files containing placeholder URLs" - - if [ "$file_count" -gt 0 ]; then - while IFS= read -r file; do - # Show relative path for better readability - local rel_path="${file#$app_dir/}" - echo " πŸ”„ $rel_path" - - # Replace all URLs - compatible with both GNU and BSD sed - # Create temp file, replace, then move - sed -e "s|$PLACEHOLDER_API|$API_URI|g" \ - -e "s|$PLACEHOLDER_DASHBOARD|$DASHBOARD_URI|g" \ - -e "s|$PLACEHOLDER_LANDING|$LANDING_URI|g" \ - -e "s|$PLACEHOLDER_WIKI|$WIKI_URI|g" \ - "$file" > "${file}.tmp" && mv "${file}.tmp" "$file" - done < "$temp_file_list" - - echo " βœ… Successfully replaced URLs in $file_count files" - else - echo " ⚠️ No files found with placeholder URLs" - fi - - rm -f "$temp_file_list" - - # Replace URLs in sitemap.xml and robots.txt (generated by next-sitemap) - echo " πŸ—ΊοΈ Processing sitemap and robots.txt..." - - # Find all sitemap files (sitemap.xml, sitemap-0.xml, etc.) - find "$app_dir/public" -type f \( -name "sitemap*.xml" -o -name "robots.txt" \) 2>/dev/null | while IFS= read -r sitemap_file; do - if [ -f "$sitemap_file" ]; then - local rel_path="${sitemap_file#$app_dir/}" - echo " πŸ”„ $rel_path" - sed -e "s|$PLACEHOLDER_API|$API_URI|g" \ - -e "s|$PLACEHOLDER_DASHBOARD|$DASHBOARD_URI|g" \ - -e "s|$PLACEHOLDER_LANDING|$LANDING_URI|g" \ - -e "s|$PLACEHOLDER_WIKI|$WIKI_URI|g" \ - "$sitemap_file" > "${sitemap_file}.tmp" && mv "${sitemap_file}.tmp" "$sitemap_file" - fi - done - - # Special handling for wiki: also replace URLs in openapi.local.json - if [ "$app" = "wiki" ]; then - local openapi_file="$app_dir/openapi.local.json" - if [ -f "$openapi_file" ]; then - echo " πŸ”„ Replacing URLs in openapi.local.json" - sed -e "s|$PLACEHOLDER_API|$API_URI|g" \ - -e "s|$PLACEHOLDER_DASHBOARD|$DASHBOARD_URI|g" \ - -e "s|$PLACEHOLDER_LANDING|$LANDING_URI|g" \ - -e "s|$PLACEHOLDER_WIKI|$WIKI_URI|g" \ - "$openapi_file" > "${openapi_file}.tmp" && mv "${openapi_file}.tmp" "$openapi_file" - echo " βœ… Updated OpenAPI spec with runtime URLs" - fi - fi - - # Also generate .env file for server-side Next.js standalone - cat > "$app_dir/.env" << EOF -# Runtime environment configuration -# Generated at container startup from environment variables -API_URI=${API_URI} -DASHBOARD_URI=${DASHBOARD_URI} -LANDING_URI=${LANDING_URI} -WIKI_URI=${WIKI_URI} -EOF - - echo "βœ… URL replacement complete for $app" -} - -# Replace URLs in all Next.js apps +# Replace URLs in all Next.js apps using optimized function +# This uses pre-generated manifests from build time instead of scanning all files replace_urls_in_app "web" "/app/apps/web/.next/standalone/apps/web" replace_urls_in_app "landing" "/app/apps/landing/.next/standalone/apps/landing" replace_urls_in_app "wiki" "/app/apps/wiki/.next/standalone/apps/wiki" diff --git a/docker/generate-url-manifest.sh b/docker/generate-url-manifest.sh new file mode 100644 index 0000000..942d553 --- /dev/null +++ b/docker/generate-url-manifest.sh @@ -0,0 +1,46 @@ +#!/bin/sh +set -e + +# Generate URL Replacement Manifest +# This script runs at BUILD TIME to create a list of files that contain placeholder URLs +# This eliminates the need to scan all files at container startup + +APP_NAME=$1 +APP_DIR=$2 + +if [ -z "$APP_NAME" ] || [ -z "$APP_DIR" ]; then + echo "Usage: $0 " + echo "Example: $0 web /app/apps/web" + exit 1 +fi + +echo "πŸ” Generating URL manifest for $APP_NAME..." + +# Define placeholder URLs that will be replaced at runtime +PLACEHOLDER_API="https://api.useplunk.com" +PLACEHOLDER_DASHBOARD="https://app.useplunk.com" +PLACEHOLDER_LANDING="https://www.useplunk.com" +PLACEHOLDER_WIKI="https://docs.useplunk.com" + +# Output manifest file +MANIFEST_FILE="$APP_DIR/.next/url-manifest.txt" + +# Find all files that contain placeholder URLs and save to manifest +# Search in .next directory for relevant file types +find "$APP_DIR/.next" -type f \( -name "*.js" -o -name "*.json" -o -name "*.html" -o -name "*.rsc" \) \ + -exec grep -l -E "$PLACEHOLDER_API|$PLACEHOLDER_DASHBOARD|$PLACEHOLDER_LANDING|$PLACEHOLDER_WIKI" {} \; \ + 2>/dev/null > "$MANIFEST_FILE" || true + +# Count files +FILE_COUNT=$(wc -l < "$MANIFEST_FILE" | tr -d ' ') + +echo "βœ… Generated manifest for $APP_NAME: $FILE_COUNT files need URL replacement" +echo " Manifest saved to: $MANIFEST_FILE" + +# Also generate manifest for sitemap/robots files +SITEMAP_MANIFEST_FILE="$APP_DIR/.next/sitemap-manifest.txt" +find "$APP_DIR/public" -type f \( -name "sitemap*.xml" -o -name "robots.txt" \) 2>/dev/null > "$SITEMAP_MANIFEST_FILE" || true +SITEMAP_COUNT=$(wc -l < "$SITEMAP_MANIFEST_FILE" | tr -d ' ') +echo "βœ… Generated sitemap manifest for $APP_NAME: $SITEMAP_COUNT files" + +exit 0 diff --git a/docker/replace-urls-optimized.sh b/docker/replace-urls-optimized.sh new file mode 100644 index 0000000..5bef0f7 --- /dev/null +++ b/docker/replace-urls-optimized.sh @@ -0,0 +1,118 @@ +#!/bin/sh +set -e + +# Optimized URL Replacement Script +# Uses pre-generated manifests to avoid expensive find+grep operations at runtime + +replace_urls_in_app() { + local app=$1 + local app_dir=$2 + + echo "πŸ“ Replacing URLs in $app build files..." + + # Define placeholder URLs (built into the app at compile time) + local PLACEHOLDER_API="https://api.useplunk.com" + local PLACEHOLDER_DASHBOARD="https://app.useplunk.com" + local PLACEHOLDER_LANDING="https://www.useplunk.com" + local PLACEHOLDER_WIKI="https://docs.useplunk.com" + + # Use pre-generated manifest instead of scanning all files + local manifest_file="$app_dir/.next/url-manifest.txt" + + if [ ! -f "$manifest_file" ]; then + echo " ⚠️ Warning: Manifest file not found at $manifest_file" + echo " This suggests the build process didn't complete correctly." + echo " Falling back to runtime file scanning (slower)..." + + # Fallback to old method if manifest doesn't exist + local temp_file_list="/tmp/replace_urls_${app}.txt" + find "$app_dir/.next" -type f \( -name "*.js" -o -name "*.json" -o -name "*.html" -o -name "*.rsc" \) \ + -exec grep -l -E "$PLACEHOLDER_API|$PLACEHOLDER_DASHBOARD|$PLACEHOLDER_LANDING|$PLACEHOLDER_WIKI" {} \; \ + 2>/dev/null > "$temp_file_list" || true + manifest_file="$temp_file_list" + fi + + # Count files from manifest + local file_count=$(wc -l < "$manifest_file" | tr -d ' ') + echo " πŸ“Š Processing $file_count files from manifest" + + if [ "$file_count" -gt 0 ]; then + # Process files using xargs for better performance + # Use -P 4 to process up to 4 files in parallel + # This significantly speeds up processing of many files + cat "$manifest_file" | xargs -P 4 -I {} sh -c ' + file="{}" + if [ -f "$file" ]; then + sed -e "s|'"$PLACEHOLDER_API"'|'"$API_URI"'|g" \ + -e "s|'"$PLACEHOLDER_DASHBOARD"'|'"$DASHBOARD_URI"'|g" \ + -e "s|'"$PLACEHOLDER_LANDING"'|'"$LANDING_URI"'|g" \ + -e "s|'"$PLACEHOLDER_WIKI"'|'"$WIKI_URI"'|g" \ + "$file" > "${file}.tmp" && mv "${file}.tmp" "$file" + fi + ' + + echo " βœ… Successfully replaced URLs in $file_count files" + else + echo " ℹ️ No files found with placeholder URLs" + fi + + # Process sitemap and robots.txt from manifest + echo " πŸ—ΊοΈ Processing sitemap and robots.txt..." + local sitemap_manifest="$app_dir/.next/sitemap-manifest.txt" + + if [ -f "$sitemap_manifest" ]; then + while IFS= read -r sitemap_file; do + if [ -f "$sitemap_file" ]; then + local rel_path="${sitemap_file#$app_dir/}" + sed -e "s|$PLACEHOLDER_API|$API_URI|g" \ + -e "s|$PLACEHOLDER_DASHBOARD|$DASHBOARD_URI|g" \ + -e "s|$PLACEHOLDER_LANDING|$LANDING_URI|g" \ + -e "s|$PLACEHOLDER_WIKI|$WIKI_URI|g" \ + "$sitemap_file" > "${sitemap_file}.tmp" && mv "${sitemap_file}.tmp" "$sitemap_file" + fi + done < "$sitemap_manifest" + else + # Fallback to finding sitemaps at runtime + find "$app_dir/public" -type f \( -name "sitemap*.xml" -o -name "robots.txt" \) 2>/dev/null | while IFS= read -r sitemap_file; do + if [ -f "$sitemap_file" ]; then + sed -e "s|$PLACEHOLDER_API|$API_URI|g" \ + -e "s|$PLACEHOLDER_DASHBOARD|$DASHBOARD_URI|g" \ + -e "s|$PLACEHOLDER_LANDING|$LANDING_URI|g" \ + -e "s|$PLACEHOLDER_WIKI|$WIKI_URI|g" \ + "$sitemap_file" > "${sitemap_file}.tmp" && mv "${sitemap_file}.tmp" "$sitemap_file" + fi + done + fi + + # Special handling for wiki: also replace URLs in openapi.local.json + if [ "$app" = "wiki" ]; then + local openapi_file="$app_dir/openapi.local.json" + if [ -f "$openapi_file" ]; then + echo " πŸ”„ Replacing URLs in openapi.local.json" + sed -e "s|$PLACEHOLDER_API|$API_URI|g" \ + -e "s|$PLACEHOLDER_DASHBOARD|$DASHBOARD_URI|g" \ + -e "s|$PLACEHOLDER_LANDING|$LANDING_URI|g" \ + -e "s|$PLACEHOLDER_WIKI|$WIKI_URI|g" \ + "$openapi_file" > "${openapi_file}.tmp" && mv "${openapi_file}.tmp" "$openapi_file" + echo " βœ… Updated OpenAPI spec with runtime URLs" + fi + fi + + # Generate .env file for server-side Next.js standalone + cat > "$app_dir/.env" << EOF +# Runtime environment configuration +# Generated at container startup from environment variables +API_URI=${API_URI} +DASHBOARD_URI=${DASHBOARD_URI} +LANDING_URI=${LANDING_URI} +WIKI_URI=${WIKI_URI} +EOF + + echo "βœ… URL replacement complete for $app" +} + +# Export the function so it can be sourced by other scripts +if [ "$1" != "" ]; then + # If called directly with arguments, execute the function + replace_urls_in_app "$1" "$2" +fi