145 lines
4 KiB
Markdown
145 lines
4 KiB
Markdown
# 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
|
|
|