# Docker Build Fix Summary ## Issue When deploying to Google Cloud, the application failed with: ``` Error: Cannot find module '/app/apps/api/dist/index.js' ``` ## Root Causes ### 1. Missing `.dockerignore` - Local `dist/` folders were being copied into the Docker build context - This caused nested `dist/dist/` structures and incorrect file locations ### 2. TypeScript Compilation Structure - `tsconfig.json` was missing `rootDir: "./src"` - TypeScript compiled `src/index.ts` to `dist/src/index.js` instead of `dist/index.js` - The start script expected `dist/index.js` ### 3. pnpm Symlink Issues - Docker's `COPY` command was not preserving pnpm's symlink structure - `node_modules` were copied but symlinks to packages in `.pnpm/` were lost - Node.js couldn't resolve packages like `@hono/node-server` ## Solutions Implemented ### 1. Created `.dockerignore` at Repository Root ``` **/dist **/node_modules **/__tests__ # ... and other build artifacts ``` This ensures Docker doesn't copy local build artifacts into the image. ### 2. Updated `apps/api/tsconfig.json` Added: ```json { "compilerOptions": { "rootDir": "./src", // ... }, "include": ["src/**/*"] } ``` This ensures TypeScript compiles `src/index.ts` → `dist/index.js` (not `dist/src/index.js`). ### 3. Fixed Dockerfile Dependency Installation Changed from copying `node_modules` to installing them fresh in the final stage: **Before:** ```dockerfile COPY --from=prod-deps /app/node_modules ./node_modules ``` **After:** ```dockerfile # Copy built files and workspace structure COPY --from=build /app/apps/api/dist ./apps/api/dist COPY --from=build /app/apps/api/package.json ./apps/api/package.json COPY --from=prod-deps /app/packages ./packages # Install dependencies with proper symlinks RUN pnpm install --frozen-lockfile --prod --filter @xtablo/api... ``` This ensures pnpm creates proper symlinks in `/app/apps/api/node_modules/` that point to packages in `/app/node_modules/.pnpm/`. ### 4. Updated `cloudbuild.yaml` Changed build command to run from monorepo root: **Before:** ```yaml args: [ 'build', '--target', '$_NODE_ENV', '-t', '...', 'apps/api' ] ``` **After:** ```yaml args: [ 'build', '-f', 'apps/api/Dockerfile', '--target', '$_NODE_ENV', '-t', '...', '.' ] ``` ## Verification ### Module Resolution Test ```bash docker run --rm --entrypoint sh xtablo-api:production -c \ "ls -la /app/apps/api/node_modules/@hono/" ``` Shows proper symlinks: ``` lrwxrwxrwx node-server -> ../../../../node_modules/.pnpm/@hono+node-server@... ``` ### Application Start Test ```bash docker run --rm -e SUPABASE_URL=test -e SUPABASE_SERVICE_ROLE_KEY=test \ xtablo-api:production ``` Application starts successfully (fails only on missing GCP credentials, not module resolution). ## Final Image Details - **Size**: 1GB (production) - **Node.js**: 18.20.8 - **pnpm**: 10.19.0 - **User**: nodejs (non-root, UID 1001) - **Working Directory**: `/app/apps/api` - **Module Structure**: Proper pnpm workspace with symlinks ## Files Modified 1. `/apps/api/Dockerfile` - Updated multi-stage build 2. `/apps/api/tsconfig.json` - Added `rootDir` and `include` 3. `/apps/api/cloudbuild.yaml` - Fixed build context 4. `/.dockerignore` - Created to exclude build artifacts 5. `/apps/api/DOCKER_BUILD.md` - Added comprehensive documentation ## Key Takeaways 1. **Always use `.dockerignore`** to prevent local artifacts from contaminating Docker builds 2. **pnpm symlinks require special handling** - install dependencies in the final stage rather than copying 3. **TypeScript `rootDir` matters** - set it explicitly to control output structure 4. **Monorepo builds must run from root** - use `-f` flag to specify Dockerfile location ## Testing in Production The image is now ready for deployment. All module resolution issues are fixed. Make sure to provide proper environment variables at runtime: ```bash docker run -p 8080:8080 \ -e SUPABASE_URL=... \ -e SUPABASE_SERVICE_ROLE_KEY=... \ -e STREAM_API_KEY=... \ -e STREAM_SECRET=... \ # ... other env vars xtablo-api:production ``` ## Date Fixed: November 13, 2024