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.jsonwas missingrootDir: "./src"- TypeScript compiled
src/index.tstodist/src/index.jsinstead ofdist/index.js - The start script expected
dist/index.js
3. pnpm Symlink Issues
- Docker's
COPYcommand was not preserving pnpm's symlink structure node_moduleswere 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.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:
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
/apps/api/Dockerfile- Updated multi-stage build/apps/api/tsconfig.json- AddedrootDirandinclude/apps/api/cloudbuild.yaml- Fixed build context/.dockerignore- Created to exclude build artifacts/apps/api/DOCKER_BUILD.md- Added comprehensive documentation
Key Takeaways
- Always use
.dockerignoreto prevent local artifacts from contaminating Docker builds - pnpm symlinks require special handling - install dependencies in the final stage rather than copying
- TypeScript
rootDirmatters - set it explicitly to control output structure - Monorepo builds must run from root - use
-fflag 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