xtablo-source/docs/DOCKER_FIX_SUMMARY.md
Arthur Belleville 247bc8b3af
Add docs
2025-11-14 09:14:25 +01:00

4 KiB

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
  • 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:

{
  "compilerOptions": {
    "rootDir": "./src",
    // ...
  },
  "include": ["src/**/*"]
}

This ensures TypeScript compiles src/index.tsdist/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:

COPY --from=prod-deps /app/node_modules ./node_modules

After:

# 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:

args: [ 'build', '--target', '$_NODE_ENV', '-t', '...', 'apps/api' ]

After:

args: [ 'build', '-f', 'apps/api/Dockerfile', '--target', '$_NODE_ENV', '-t', '...', '.' ]

Verification

Module Resolution Test

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

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:

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