# Base image FROM python:3.12.3-slim-bookworm as base # Install Nginx, curl, and build-essential RUN apt update && apt install -y nginx curl build-essential \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* \ && rm /etc/nginx/sites-enabled/default # Install Node tools RUN curl -fsSL https://deb.nodesource.com/setup_21.x | bash - \ && apt-get install -y nodejs \ && npm install -g corepack pm2 # Frontend Build FROM base AS deps ARG NEXT_PUBLIC_LEARNHOUSE_API_URL ARG NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL ARG NEXT_PUBLIC_LEARNHOUSE_DOMAIN ENV NEXT_PUBLIC_LEARNHOUSE_API_URL=${NEXT_PUBLIC_LEARNHOUSE_API_URL} ENV NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL=${NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL} ENV NEXT_PUBLIC_LEARNHOUSE_DOMAIN=${NEXT_PUBLIC_LEARNHOUSE_DOMAIN} WORKDIR /app/web COPY ./apps/web/package.json ./apps/web/pnpm-lock.yaml* ./ COPY ./apps/web /app/web RUN rm -f .env* # Patch TypeScript issue before build - add null check for useSearchParams RUN find . -name "*.tsx" -exec sed -i 's/const searchParams = useSearchParams()/const searchParams = useSearchParams()\?.get/g' {} \; RUN find . -name "*.tsx" -exec sed -i 's/searchParams\.get/searchParams/g' {} \; # Allow build to continue with type errors RUN if [ -f pnpm-lock.yaml ]; then \ corepack enable pnpm && \ pnpm i --frozen-lockfile && \ pnpm add @types/react@latest @types/node@latest next-navigation@latest --save-dev && \ NEXT_IGNORE_TYPE_ERROR=1 pnpm run build; \ else echo "Lockfile not found." && exit 1; \ fi # Final image FROM base as runner RUN addgroup --system --gid 1001 system \ && adduser --system --uid 1001 app \ && mkdir .next \ && chown app:system .next COPY --from=deps /app/web/public ./app/web/public COPY --from=deps --chown=app:system /app/web/.next/standalone ./app/web/ COPY --from=deps --chown=app:system /app/web/.next/static ./app/web/.next/static # Backend Build WORKDIR /app/api COPY ./apps/api/pyproject.toml ./ COPY ./apps/api/uv.lock ./ # Install dependencies with proper flags and fallback to pip if needed RUN pip install --upgrade pip && \ pip install uv && \ (uv pip sync --system --python=python3.12 || pip install -e .) && \ pip install uvicorn # Ensure at least the ASGI server is available COPY ./apps/api ./ # Run the backend WORKDIR /app COPY ./extra/nginx.conf /etc/nginx/conf.d/default.conf ENV PORT=80 LEARNHOUSE_PORT=9000 HOSTNAME=0.0.0.0 COPY ./extra/start.sh /app/start.sh RUN chmod +x /app/start.sh # Add runtime configuration and enhanced patching RUN echo '#!/bin/bash\n\ echo "Generating runtime configuration..."\n\ mkdir -p /app/web/public\n\ cat > /app/web/public/runtime-config.js << EOF\n\ window.RUNTIME_CONFIG = {\n\ LEARNHOUSE_API_URL: "${NEXT_PUBLIC_LEARNHOUSE_API_URL:-}",\n\ LEARNHOUSE_BACKEND_URL: "${NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL:-}",\n\ LEARNHOUSE_DOMAIN: "${NEXT_PUBLIC_LEARNHOUSE_DOMAIN:-}",\n\ LEARNHOUSE_DEFAULT_ORG: "${NEXT_PUBLIC_LEARNHOUSE_DEFAULT_ORG:-default}",\n\ LEARNHOUSE_MULTI_ORG: "${NEXT_PUBLIC_LEARNHOUSE_MULTI_ORG:-false}",\n\ LEARNHOUSE_TOP_DOMAIN: "${NEXT_PUBLIC_LEARNHOUSE_TOP_DOMAIN:-}"\n\ }\n\ EOF\n\ \n\ echo "Runtime configuration generated successfully"\n\ \n\ # Create a utility script to override fetch for better URL control\n\ cat > /app/web/public/api-interceptor.js << EOF\n\ (function() {\n\ // Get the current domain\n\ const currentDomain = window.location.hostname;\n\ console.log("Current domain:", currentDomain);\n\ \n\ // Check if RUNTIME_CONFIG is available\n\ if (!window.RUNTIME_CONFIG) {\n\ console.error("Runtime config not found!");\n\ window.RUNTIME_CONFIG = {};\n\ }\n\ \n\ // Save the original fetch function\n\ const originalFetch = window.fetch;\n\ \n\ // Override fetch to enforce current domain\n\ window.fetch = function(url, options) {\n\ if (typeof url === "string") {\n\ // Check if URL contains a domain that doesnt match current domain\n\ const urlObj = new URL(url, window.location.origin);\n\ const targetDomain = urlObj.hostname;\n\ \n\ // If URL has a different domain than current domain, change it\n\ if (targetDomain !== currentDomain && url.includes("/api/")) {\n\ console.warn("Redirecting API request to current domain:", url);\n\ const newUrl = url.replace(targetDomain, currentDomain);\n\ console.log("New URL:", newUrl);\n\ return originalFetch(newUrl, options);\n\ }\n\ }\n\ \n\ // Call the original fetch with unchanged URL\n\ return originalFetch(url, options);\n\ };\n\ \n\ console.log("API interceptor installed successfully");\n\ })();\n\ EOF\n\ \n\ echo "Enhanced patching of NextAuth cookies and domains..."\n\ find /app/web/.next -type f -name "*.js" -exec sed -i "s/domain:[^,}]*,/domain: undefined,/g" {} \\;\n\ find /app/web/.next -type f -name "*.js" -exec sed -i "s/domain: *process.env.LEARNHOUSE_COOKIE_DOMAIN/domain: undefined/g" {} \\;\n\ find /app/web/.next -type f -name "*.js" -exec sed -i "s/\.domain\s*=\s*[^;]*;/\.domain = undefined;/g" {} \\;\n\ echo "Cookie domain patches complete."\n\ \n\ echo "Starting application..."\n\ sh /app/start.sh' > /app/runtime-config-start.sh && chmod +x /app/runtime-config-start.sh # Use the runtime config script CMD ["/app/runtime-config-start.sh"]