
Dockerfile Skill
- 50 installs
- 1 repo stars
- Updated June 18, 2026
- zjy365/seakills
Helps with devops & ci/cd tasks during AI-assisted development.
About
dockerfile-skill is a Claude Code skill for devops & ci/cd. It helps solo builders move faster with AI-assisted coding.
- dockerfile-skill
- DevOps & CI/CD
- AI-coding skill
Dockerfile Skill by the numbers
- 50 all-time installs (skills.sh)
- Ranked #730 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/zjy365/seakills --skill dockerfile-skillAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 50 |
|---|---|
| repo stars | ★ 1 |
| Last updated | June 18, 2026 |
| Repository | zjy365/seakills ↗ |
What it does
Helps with devops & ci/cd tasks during AI-assisted development.
Files
Dockerfile Generator Skill
Overview
This skill generates production-ready Dockerfiles through a 4-phase process: 1. Deep Analysis - Understand project structure, workspace, migrations, and build complexity 2. Generate - Create Dockerfile with migration handling and build optimization 3. Build & Fix - Validate through actual build, fix errors iteratively 4. Runtime Validation - Verify migrations ran, app works, database populated
Key Capabilities
- Workspace/Monorepo Support: pnpm workspace, Turborepo, npm workspaces
- Custom CLI Detection: Auto-detect custom build CLIs (turbo, nx, lerna, rush, or project-specific) and use correct syntax
- Git Hash Bypass: Detect and handle projects requiring git commit hash (GITHUB_SHA)
- Build-Time Env Vars: Auto-detect and add placeholders for Next.js SSG
- Error Pattern Database: 40+ known error patterns with automatic fixes
- Smart .dockerignore: Avoid excluding workspace-required files and CLI config dependencies
- Custom Entry Points: Support for custom server launchers
- Migration Detection: Auto-detect ORM, migrations, handle standalone mode
- Build Optimization: Skip heavy CI tasks (lint/type-check) to prevent OOM
- Runtime Validation: Verify migrations ran, database populated, app working
- Native Module Support: Auto-detect Rust/NAPI-RS modules, multi-architecture builds
- Static Asset Mapping: Detect backend's expected static paths and map frontend outputs
- External Services: Auto-detect PostgreSQL, Redis, MinIO, ManticoreSearch dependencies
- Zero Human Interaction: Auto-generate all config files including secrets
Usage
/dockerfile # Analyze current directory
/dockerfile <github-url> # Clone and analyze GitHub repo
/dockerfile <path> # Analyze specific pathQuick Start
When invoked, ALWAYS follow this sequence:
1. Read and execute modules/analyze.md 2. Read and execute modules/generate.md 3. Read and execute modules/build-fix.md
Workflow
Phase 1: Deep Project Analysis
Load and execute: modules/analyze.md
Output: Structured project metadata including:
- Language / Framework / Package manager
- Build commands / Run commands / Port
- External dependencies (DB/Redis/S3)
- System library requirements
- Migration system detection (ORM, migration count, execution method)
- Build complexity analysis (heavy operations, memory risk)
- Complexity level (L1/L2/L3)
Phase 2: Generate Dockerfile
Load and execute: modules/generate.md
Input: Analysis result from Phase 1 Output:
Dockerfile(with migration handling, build optimization).dockerignore(workspace-aware)docker-compose.yml(if external services needed).env.docker.local(auto-generated with test secrets)docker-entrypoint.sh(with migration execution)DOCKER.md(complete deployment guide)- Environment variable documentation
Key Enhancements:
- Auto-detect Next.js Standalone + ORM → separate deps installation
- Auto-detect heavy build operations → optimized build command
- Auto-generate all config files → zero user input required
Phase 3: Build Validation (Closed Loop)
Load and execute: modules/build-fix.md
Process: 1. Execute docker buildx build --platform linux/amd64 --load 2. If success → Proceed to Phase 4 3. If failure → Parse error, match pattern, fix Dockerfile, retry 4. Max iterations based on complexity level
Phase 4: Runtime Validation
Critical Addition: Don't declare success until runtime verification passes!
Validation Steps: 1. Container Startup: docker-compose up -d and verify no crashes 2. Database Migration:
- Query database:
psql -c "\dt"→ verify tables exist - Check migration count matches expected (e.g., 76/76)
- Verify no "relation does not exist" errors
3. Application Health:
- Test HTTP endpoint → 200/302/401 acceptable, 500 is failure
- Check logs for errors
- Verify health check endpoint
4. Success Criteria: Only declare success if ALL pass
Why This Matters:
- Previous: Declared success after
docker build, but app didn't work at runtime - Now: Verify migrations ran, database populated, app actually functional
- Prevents silent migration failures (e.g., standalone mode missing ORM deps)
Supporting Resources
- Templates: templates/ - Base Dockerfile templates by tech stack
- Error Patterns: knowledge/error-patterns.md - Known errors and fixes
- System Dependencies: knowledge/system-deps.md - NPM/Pip package → system library mapping
- Best Practices: knowledge/best-practices.md - Docker production best practices
- Output Format: examples/output-format.md - Expected output structure
Complexity Levels
| Level | Criteria | Max Build Iterations |
|---|---|---|
| L1 | Single language, no build step, no external services, no migrations | 1 |
| L2 | Has build step, has external services (DB/Redis), simple migrations | 3 |
| L3 | Monorepo, multi-language, complex dependencies, build-time env vars, complex migrations (76+) | 5 |
Common Issues & Solutions
1. Database migrations not running - MOST CRITICAL
Symptom: relation "users" does not exist at runtime Cause: Migrations detected but never executed Prevention: Analysis phase Step 12 detects migrations and configures execution Fix:
- For Standalone + ORM: Install ORM deps separately
- Add runtime migration to entrypoint script
- Verify with
psql -c "\dt"after container starts
2. Out of Memory during build
Symptom: Exit code 137, Killed, heap out of memory Cause: Build script includes lint/type-check for 39+ workspace packages Prevention: Analysis phase Step 13 detects heavy operations Fix: Skip CI tasks in Docker build, increase NODE_OPTIONS to 8192MB
3. Workspace files not found
Symptom: ENOENT: no such file or directory, open '/app/e2e/package.json' Cause: .dockerignore excludes workspace package.json files Fix: Use e2e/* instead of e2e, then !e2e/package.json
4. lockfile=false projects
Symptom: Cannot generate lockfile because lockfile is set to false Cause: Project has lockfile=false in .npmrc Fix: Use pnpm install instead of pnpm install --frozen-lockfile
5. Build-time env vars missing
Symptom: KEY_VAULTS_SECRET is not set Cause: Next.js SSG needs env vars at build time Fix: Add ARG/ENV placeholders in build stage
6. Node binary path
Symptom: spawn /bin/node ENOENT Cause: Scripts hardcode /bin/node but node:slim has it at /usr/local/bin/node Fix: Add RUN ln -sf /usr/local/bin/node /bin/node
7. ORM not found in Standalone mode
Symptom: Cannot find module 'drizzle-orm' at runtime Cause: Next.js standalone doesn't include all node_modules Prevention: Analysis phase detects standalone + ORM combination Fix: Install ORM separately in /deps and copy to final image
8. Wrong build command for monorepo with custom CLI
Symptom: Build succeeds but output files missing (e.g., assets-manifest.json not found) Cause: Using yarn workspace @scope/pkg build instead of detected custom CLI syntax Prevention: Analysis phase Step 14 detects custom CLI Fix: Use detected CLI syntax for all build commands
9. Git hash required but .git not in Docker context
Symptom: Failed to open git repo or nodegit errors Cause: Build tool requires git commit hash for versioning Prevention: Analysis phase Step 14 detects git hash dependency Fix: Set ENV GITHUB_SHA=docker-build to bypass git requirement
10. CLI config files excluded by .dockerignore
Symptom: CLI initialization (e.g., ${CLI_NAME} init) fails silently Cause: .prettierrc, .prettierignore, or other config files excluded Prevention: Analysis phase Step 14 detects config file dependencies Fix: Remove config files from .dockerignore exclusions
11. Static assets not found at runtime
Symptom: ENOENT: no such file or directory, open '/app/static/assets-manifest.json' Cause: Frontend builds to different path than backend expects Prevention: Analysis phase Step 14 detects static asset path mapping Fix: Copy frontend outputs to backend's expected path in Dockerfile
Success Criteria
A successful Dockerfile must:
Build Phase: 1. Build without errors (docker buildx build exits 0) 2. Image size reasonable (< 2GB for most apps) 3. Follow production best practices (multi-stage, non-root, fixed versions) 4. Include all necessary supporting files (.dockerignore, docker-compose.yml, etc.) 5. Handle all workspace/monorepo requirements
Runtime Phase - CRITICAL: 6. Container starts successfully (no crashes) 7. Database migrations execute successfully (if migrations detected) 8. Database tables created (verify with psql) 9. Application responds with valid HTTP codes (200/302/401, not 500) 10. No runtime errors in logs (no "relation does not exist", etc.)
DO NOT declare success if:
- Build passes but runtime fails
- Migrations detected but tables missing
- App returns 500 errors
- Logs show database relation errors
Post-Build Validation COMPREHENSIVE
After successful build, perform FULL validation:
# 1. Start services
docker-compose up -d
sleep 30 # Wait for startup
# 2. Check container status
docker-compose ps
# Expected: All containers UP and HEALTHY
# 3. Verify database migrations
if [ migrations_detected ]; then
# List tables
docker-compose exec postgres psql -U <user> -d <db> -c "\dt"
# Expected: List of tables (users, sessions, etc.)
# If "Did not find any relations" → FAIL
# Count migrations
MIGRATION_COUNT=$(docker-compose exec postgres psql -U <user> -d <db> -t -c "SELECT COUNT(*) FROM <migration_table>;")
# Expected: Matches analysis count (e.g., 76)
fi
# 4. Test application health
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3210)
# Expected: 200, 302, or 401
# Unacceptable: 500, 502, 503
if [ "$HTTP_CODE" = "500" ]; then
echo "FAILURE: App returning 500 error"
docker-compose logs app
exit 1
fi
# 5. Check for errors in logs
docker-compose logs app | grep -i "error" | tail -20
# Should NOT contain:
# - "relation does not exist"
# - "table not found"
# - "Cannot find module"
# 6. Check image size
docker images <image-name>
# 7. Cleanup (if needed)
docker-compose downValidation Checklist:
- [ ] Image built successfully
- [ ] Container started without crashes
- [ ] Database connection established
- [ ] Migrations executed (if applicable)
- [ ] Database tables exist (if applicable)
- [ ] HTTP endpoint returns valid status
- [ ] No errors in application logs
- [ ] Health check passes
Output Format Examples
Successful Build Output
## Dockerfile Generation Complete
### Project Analysis
- **Language**: Node.js (TypeScript)
- **Framework**: Next.js 14
- **Package Manager**: pnpm
- **Complexity**: L2 (Medium)
- **Detected Port**: 3000
### Generated Files
#### Dockerfilesyntax=docker/dockerfile:1.4
FROM node:20.11.1-slim AS deps ...
#### .dockerignore.git node_modules .next ...
#### docker-compose.ymlservices: app: build: . ports:
- "3000:3000"
...
### Build Results
Build successful!
**Image**: `your-app:latest`
**Size**: ~245MB
### Quick Start
Build
docker buildx build --platform linux/amd64 --load -t your-app:latest .
Run
docker run -d -p 3000:3000 your-app:latest
With docker-compose
docker-compose up -d
### Environment Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `DATABASE_URL` | Yes | - | PostgreSQL connection string |
| `PORT` | No | 3000 | Server port |---
Build with Fixes Output
## Dockerfile Generation Complete
### Project Analysis
- **Language**: Node.js (TypeScript)
- **Framework**: Next.js 14
- **Package Manager**: npm
- **Complexity**: L3 (High)
- **Detected Port**: 3000
### Build Iterations
#### Iteration 1: Failed
**Error**: `ENOENT: no such file or directory, open '/app/config/config.json'`
**Fix Applied**: Added `RUN mkdir -p /app/config && echo '{}' > /app/config/config.json`
#### Iteration 2: Failed
**Error**: `Error: DATABASE_URL environment variable is required`
**Fix Applied**: Added `ARG DATABASE_URL=postgres://placeholder` and `ENV DATABASE_URL=$DATABASE_URL`
#### Iteration 3: Success
### Generated Files
[files output...]
### Build Results
Build successful after 3 iterations
### Fixes Applied
1. Created missing config directory and placeholder config.json
2. Added DATABASE_URL build-time placeholder for SSG compatibility
### Notes
- The DATABASE_URL placeholder is used only during build
- Provide actual DATABASE_URL at runtime via environment variable---
Build Failed Output
## Dockerfile Generation - Manual Review Required
### Project Analysis
- **Language**: Node.js
- **Framework**: Custom
- **Complexity**: L3 (High)
### Build Iterations
#### Iteration 1-5: Failed
**Final Error**:Error: Cannot find module '@company/internal-package'
### Attempted Fixes
1. Added node-gyp build dependencies
2. Increased NODE_OPTIONS memory
3. Added missing directories
### Manual Steps Required
The build failed due to a private npm package that requires authentication.
**To fix**:
1. Add `.npmrc` with registry authentication://npm.pkg.github.com/:_authToken=${NPM_TOKEN} @company:registry=https://npm.pkg.github.com
2. Update Dockerfile to use build secrets:RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
3. Build with:docker buildx build --platform linux/amd64 --load --secret id=npmrc,src=.npmrc -t app .
### Partial Output
The best working version of Dockerfile has been saved. It may work with the above modifications.---
Analysis-Only Output (for debugging)
## Project Analysis Results
### Detection Summary
| Property | Value |
|----------|-------|
| Language | Node.js |
| Runtime Version | 20.x |
| Framework | Next.js 14.1.0 |
| Package Manager | pnpm |
| Has TypeScript | Yes |
| Has Build Step | Yes |
| Output Mode | standalone |
### Dependency Analysis
#### NPM Packages with Native Dependencies
- `sharp` → requires `libvips-dev`
- `bcrypt` → requires `python3 make g++`
#### External Services Detected
- PostgreSQL (from `@prisma/client`)
- Redis (from `ioredis`)
### Environment Variables
#### Build-time Required
| Variable | Source | Purpose |
|----------|--------|---------|
| `DATABASE_URL` | `schema.prisma` | Prisma client generation |
| `NEXT_PUBLIC_API_URL` | `next.config.js` | Public API endpoint |
#### Runtime Required
| Variable | Source | Purpose |
|----------|--------|---------|
| `DATABASE_URL` | `lib/db.ts` | Database connection |
| `REDIS_URL` | `lib/cache.ts` | Cache connection |
| `JWT_SECRET` | `lib/auth.ts` | Authentication |
### Complexity Assessment
**Level**: L3 (High)
**Reasons**:
- SSG with database access during build
- Multiple native dependencies
- External service dependencies
- Environment variable requirements during build
### Recommended Approach
1. Use multi-stage build with deps → build → runtime
2. Install `libvips-dev` and build tools in deps stage
3. Provide placeholder DATABASE_URL for build
4. Generate Prisma client before build stepDocker Best Practices
Base Image Selection
Version Pinning
# GOOD - Fixed patch version
FROM node:20.11.1-slim
FROM python:3.11.7-slim
FROM golang:1.21.6-alpine
# BAD - Floating tags
FROM node:latest
FROM node:lts
FROM node:20 # Minor version can change
FROM python:3 # Major version onlyImage Variants
| Variant | Size | Use Case |
|---|---|---|
alpine | Smallest | Go static binaries, simple apps |
slim | Small | Node.js, Python (recommended) |
bookworm/bullseye | Medium | Need full toolchain |
| Default (no suffix) | Large | Avoid in production |
Recommended Base Images
# Node.js
FROM node:20.11.1-slim
# Python
FROM python:3.11.7-slim
# Go
FROM golang:1.21.6-alpine AS builder
FROM alpine:3.19 AS runtime
# Or for scratch:
FROM scratch
# Java
FROM eclipse-temurin:21-jre-alpine
# Ruby
FROM ruby:3.2-slim---
Multi-Stage Build Pattern
Standard 3-Stage Pattern
# Stage 1: Dependencies
FROM node:20-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Stage 2: Build
FROM deps AS build
COPY . .
RUN npm run build
# Stage 3: Runtime
FROM node:20-slim AS runtime
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=deps /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]Why Multi-Stage?
1. Smaller images: Build tools don't end up in production 2. Better caching: Dependencies layer changes less often 3. Security: Less attack surface
---
Layer Optimization
Order by Change Frequency
# GOOD - Least changing first
COPY package.json package-lock.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
# BAD - Source files invalidate cache for dependencies
COPY . .
RUN npm ci && npm run buildCombine RUN Commands
# GOOD - Single layer
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
python3 \
make \
g++ \
&& rm -rf /var/lib/apt/lists/*
# BAD - Multiple layers
RUN apt-get update
RUN apt-get install -y python3
RUN apt-get install -y make
RUN apt-get install -y g++---
Cache Mounts (BuildKit)
# syntax=docker/dockerfile:1.4
# npm
RUN --mount=type=cache,target=/root/.npm \
npm ci
# yarn
RUN --mount=type=cache,target=/root/.yarn \
yarn install --frozen-lockfile
# pnpm
RUN --mount=type=cache,target=/pnpm/store \
pnpm install --frozen-lockfile
# pip
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
# go
RUN --mount=type=cache,target=/go/pkg/mod \
go build -o main .---
Security
Non-Root User
# Node.js (built-in user)
USER node
# Python (create user)
RUN useradd -m -u 1000 appuser
USER appuser
# Go/Alpine
RUN adduser -D -u 1000 appuser
USER appuser
# Or use nobody
USER nobodyFile Permissions
# Set ownership before switching user
COPY --chown=node:node . .
USER node
# Or change after copy
COPY . .
RUN chown -R node:node /app
USER nodeDon't Include Secrets
# BAD - Secret in image layer
ENV API_KEY=sk-xxxxx
COPY .env .
# GOOD - Runtime injection
# Use docker run -e or docker-compose environment---
.dockerignore
Must Ignore
.git
.env
.env.*
node_modules
__pycache__
*.pyc
.venv
vendor
dist
build
.next
coverage
*.logShould Ignore
.vscode
.idea
*.md
docs
tests
*.test.js
*.spec.ts
Makefile
docker-compose*.yml
Dockerfile*---
Health Check
Without curl (Recommended)
# Node.js
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD node -e "require('http').get('http://127.0.0.1:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
# Python
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"With curl (If available)
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1---
Environment Variables
Build-time vs Runtime
# Build-time only (not in final image)
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-slim
# Runtime (persists in image)
ENV NODE_ENV=production
ENV PORT=3000
# Build-time passed to runtime
ARG VERSION
ENV APP_VERSION=$VERSIONDefault Values
# With default
ENV PORT=3000
ENV NODE_ENV=production
# Without default (must be provided at runtime)
# Just document in comments or separate file---
Signals and Graceful Shutdown
Exec Form (Recommended)
# GOOD - PID 1, receives signals
CMD ["node", "server.js"]
ENTRYPOINT ["python", "app.py"]
# BAD - Shell wrapper, signals not forwarded
CMD node server.jsWith Init System
# For complex apps needing init
FROM node:20-slim
RUN apt-get update && apt-get install -y --no-install-recommends tini
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["node", "server.js"]---
Common Anti-Patterns
DON'T: Use ADD for URLs
# BAD
ADD https://example.com/file.tar.gz /app/
# GOOD
RUN curl -L https://example.com/file.tar.gz | tar xz -C /app/DON'T: Run apt-get upgrade
# BAD - Unpredictable results
RUN apt-get update && apt-get upgrade -y
# GOOD - Only install what you need
RUN apt-get update && apt-get install -y --no-install-recommends specific-packageDON'T: Store data in container
# BAD - Data lost on container restart
RUN mkdir /data
# GOOD - Use volumes
VOLUME ["/data"]DON'T: Hardcode paths that should be configurable
# BAD
WORKDIR /home/user/myapp
# GOOD
WORKDIR /app---
Workspace / Monorepo Best Practices
pnpm Workspace Pattern
# Stage 1: Dependencies
FROM node:20-slim AS deps
WORKDIR /app
# Enable pnpm
RUN corepack enable && corepack prepare pnpm@10.20.0 --activate
# Copy workspace configuration files
COPY package.json pnpm-workspace.yaml .npmrc ./
# Copy ALL workspace package.json files for proper resolution
COPY packages ./packages
COPY patches ./patches
COPY e2e/package.json ./e2e/
COPY apps/desktop/src/main/package.json ./apps/desktop/src/main/
# Install dependencies
# IMPORTANT: Check .npmrc for lockfile=false
# If lockfile=false, do NOT use --frozen-lockfile
RUN pnpm install --ignore-scriptsSmart .dockerignore for Workspaces
# DON'T: Exclude entire directories
e2e # This excludes e2e/package.json too!
apps/desktop
# DO: Exclude contents but keep package.json
e2e/*
!e2e/package.json
apps/desktop/node_modules
apps/desktop/dist
apps/desktop/out
# This keeps apps/desktop/src/main/package.jsonBuild-Time Environment Variables
For Next.js and similar frameworks that need env vars during static generation:
# Build stage
FROM base AS build
# Use ARG for build-time placeholders (more secure, not in final image)
ARG KEY_VAULTS_SECRET_PLACEHOLDER="build-placeholder-32chars"
ARG DATABASE_URL_PLACEHOLDER="postgres://placeholder:placeholder@localhost:5432/placeholder"
# Set as ENV for the build process
ENV KEY_VAULTS_SECRET=${KEY_VAULTS_SECRET_PLACEHOLDER}
ENV DATABASE_URL=${DATABASE_URL_PLACEHOLDER}
ENV AUTH_SECRET=${KEY_VAULTS_SECRET_PLACEHOLDER}
ENV DATABASE_DRIVER=""
# Build will now succeed even though these are placeholders
RUN npm run buildCustom Server Entry Points
For apps with custom server launchers:
# Runtime stage
FROM node:20-slim AS production
# IMPORTANT: Create symlink for scripts that expect /bin/node
RUN ln -sf /usr/local/bin/node /bin/node
# Copy custom entry point
COPY --from=build /app/scripts/serverLauncher/startServer.js ./startServer.js
COPY --from=build /app/scripts/_shared ./scripts/_shared
# If using database migrations
COPY --from=build /app/scripts/migrateServerDB/docker.cjs ./docker.cjs
COPY --from=build /app/scripts/migrateServerDB/errorHint.js ./errorHint.js
COPY --from=build /app/packages/database/migrations ./migrations
# Use custom entry point instead of server.js
CMD ["node", "startServer.js"]Files You Must NOT Exclude
When creating .dockerignore for complex projects:
# These MUST be available during build:
# ✓ .npmrc (package manager config)
# ✓ pnpm-workspace.yaml
# ✓ patches/** (pnpm patched deps)
# ✓ All workspace package.json files
# ✓ Build scripts (prebuild.mts, etc.)
# ✓ Server launcher scripts
# ✓ Migration scripts and files---
Database/External Service Patterns
PostgreSQL with pgvector
# docker-compose.yml
services:
db:
image: pgvector/pgvector:pg16 # Not just postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]Wait for Dependencies
# In app container, wait for DB to be ready
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD node -e "fetch('http://localhost:3000/api/health')"# docker-compose.yml
services:
app:
depends_on:
db:
condition: service_healthyError Pattern Knowledge Base
Pattern Format
Each pattern includes:
- regex: Pattern to match in error output
- category: Error classification
- fix: Dockerfile modification to apply
- confidence: How reliably this fix works (high/medium/low)
---
Category: File System
ENOENT - File Not Found
pattern: "ENOENT.*no such file or directory.*['\"](.+?)['\"]"
category: filesystem
confidence: high
extract: path from capture group 1
fix: |
# Before the failing RUN command, add:
RUN mkdir -p $(dirname {path}) && touch {path}
# Or for JSON config:
RUN mkdir -p $(dirname {path}) && echo '{}' > {path}ENOENT - Module Not Found
pattern: "Cannot find module ['\"](.+?)['\"]"
category: filesystem
confidence: medium
extract: module name
fix: |
# Check if it's a local file that wasn't copied
# Add to COPY if needed:
COPY {module_path} ./Directory Not Found
pattern: "ENOTDIR|directory.*not found|No such file or directory: ['\"](.+?)['\"]"
category: filesystem
confidence: high
fix: |
RUN mkdir -p {directory}---
Category: Environment Variables
Required Env Not Set
pattern: "`(.+?)` is not set|(.+?) environment variable is required|process\\.env\\.(.+?) is (not defined|undefined)"
category: environment
confidence: high
extract: variable name
fix: |
# In build stage:
ARG {VAR_NAME}=placeholder_for_build
ENV {VAR_NAME}=${{VAR_NAME}}KeyError (Python)
pattern: "KeyError: ['\"](.+?)['\"]"
category: environment
confidence: medium
extract: key name
fix: |
# Check if it's an env var access
ENV {KEY}=placeholder---
Category: Memory
JavaScript Heap OOM
pattern: "JavaScript heap out of memory|FATAL ERROR.*Allocation failed"
category: memory
confidence: high
fix: |
ENV NODE_OPTIONS="--max-old-space-size=4096"
# If already set to 4096, increase to 8192Process Killed (OOM Killer)
pattern: "Killed|Exit code: 137|signal: SIGKILL"
category: memory
confidence: high
fix: |
# For Node.js:
ENV NODE_OPTIONS="--max-old-space-size=8192"
# For general: suggest increasing Docker memory limit---
Category: Native Modules
node-gyp Build Failed
pattern: "gyp ERR!|node-gyp rebuild|Cannot find module.*node-gyp"
category: native_module
confidence: high
fix: |
# Add to deps/build stage:
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 \
make \
g++ \
&& rm -rf /var/lib/apt/lists/*GCC/G++ Missing
pattern: "command 'gcc' failed|g\\+\\+: command not found|cc: not found"
category: native_module
confidence: high
fix: |
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*Python distutils Missing
pattern: "No module named 'distutils'|ModuleNotFoundError.*distutils"
category: native_module
confidence: high
fix: |
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 \
python3-distutils \
&& rm -rf /var/lib/apt/lists/*---
Category: Package-Specific
Sharp / libvips
pattern: "sharp|vips|Something went wrong installing the \"sharp\" module"
category: package_specific
confidence: high
fix: |
# In build stage:
RUN apt-get update && apt-get install -y --no-install-recommends \
libvips-dev \
&& rm -rf /var/lib/apt/lists/*Canvas / Cairo
pattern: "canvas|cairo|pango|librsvg"
category: package_specific
confidence: high
fix: |
RUN apt-get update && apt-get install -y --no-install-recommends \
libcairo2-dev \
libpango1.0-dev \
libjpeg-dev \
libgif-dev \
librsvg2-dev \
&& rm -rf /var/lib/apt/lists/*better-sqlite3
pattern: "better-sqlite3|Could not locate the bindings file"
category: package_specific
confidence: high
fix: |
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 \
make \
g++ \
&& rm -rf /var/lib/apt/lists/*bcrypt
pattern: "bcrypt.*error|node_modules/bcrypt"
category: package_specific
confidence: high
fix: |
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 \
make \
g++ \
&& rm -rf /var/lib/apt/lists/*---
Category: Permission
EACCES Permission Denied
pattern: "EACCES.*permission denied|PermissionError.*Errno 13"
category: permission
confidence: medium
fix: |
# Before USER directive:
RUN chown -R node:node /app
# Or adjust the path in questionnpm/yarn EACCES
pattern: "npm ERR! EACCES|yarn.*EACCES"
category: permission
confidence: high
fix: |
# Ensure cache directory is writable:
RUN mkdir -p /home/node/.npm && chown -R node:node /home/node
USER node---
Category: Network
Network Timeout
pattern: "ETIMEDOUT|network timeout|request.*timed out"
category: network
confidence: medium
fix: |
# For npm:
RUN npm ci --network-timeout 600000
# For yarn:
RUN yarn install --network-timeout 600000Host Resolution Failed
pattern: "ENOTFOUND|getaddrinfo.*failed|Could not resolve host"
category: network
confidence: low
fix: |
# Usually a transient issue, retry may help
# Or add DNS configuration if persistent---
Category: Shell Syntax
Shell Syntax Error
pattern: "/bin/sh.*syntax error|unexpected (EOF|token)"
category: shell
confidence: high
fix: |
# Review RUN commands for:
# - Unescaped special characters ($, ", ', `)
# - Unclosed quotes
# - Complex command substitution
# Use heredoc for multi-line:
RUN <<EOF
command1
command2
EOFEscape Character Issues
pattern: "command not found:|unexpected.*\\$"
category: shell
confidence: medium
fix: |
# Check for proper escaping:
# $VAR → \$VAR (if literal)
# Or use single quotes---
Category: Lockfile
Lockfile Mismatch
pattern: "npm ci.*This command requires an existing lockfile|Your lock file needs to be updated"
category: lockfile
confidence: high
fix: |
# Change from npm ci to npm install:
RUN npm install
# Or for pnpm:
RUN pnpm install # Instead of --frozen-lockfileMissing Lockfile
pattern: "npm ci.*ENOENT.*package-lock.json|pnpm.*ERR_PNPM_NO_LOCKFILE"
category: lockfile
confidence: high
fix: |
# Fallback to non-frozen install:
RUN npm install
# Or:
RUN pnpm installLockfile Disabled in Config
pattern: "lockfile is set to false|Cannot generate.*lockfile.*because lockfile is set to false"
category: lockfile
confidence: high
fix: |
# Project has lockfile=false in .npmrc
# Do NOT use --frozen-lockfile
RUN pnpm install --ignore-scripts
# Instead of:
# RUN pnpm install --frozen-lockfile---
Category: Build Tool Configuration
ESLint Parse Errors (Config Missing)
pattern: "Parsing error: The keyword '(import|export|const|let|class)' is reserved|✖ \\d+ problems \\(\\d+ errors"
category: build_config
confidence: high
cause: |
Build script includes lint/eslint step, but ESLint config files
(.eslintrc.js, eslint.config.js, etc.) are excluded from Docker context.
ESLint falls back to default parser which doesn't support ES6+ syntax.
prevention: |
Should be caught in analysis phase Step 16 (Build Command Dependency Validation).
Analysis auto-detects commands with missing config files and generates
a resolved build command that skips them.
fix: |
# Identify the prebuild script that runs lint:
# e.g., "prebuild": "tsx scripts/prebuild.mts && npm run lint"
# Replace with direct script execution, skipping lint:
# Before (fails):
RUN npm run prebuild && npm run build
# After (works):
RUN npx tsx scripts/prebuild.mts && npx next build --webpack
# Add comment explaining the change:
# NOTE: Running prebuild script directly (skipping lint)
# Lint requires .eslintrc.js which is excluded in .dockerignore
# Run lint in CI/CD pipeline insteadTypeScript Config Missing
pattern: "Cannot find.*tsconfig.json|TS18003.*tsconfig.json"
category: build_config
confidence: high
fix: |
# If type-check is in build script but tsconfig.json excluded:
# Skip type-check command, run essential build steps only
# Or ensure tsconfig.json is NOT in .dockerignoreStylelint Config Missing
pattern: "Error: No configuration provided for|Could not find.*stylelintrc"
category: build_config
confidence: high
fix: |
# Skip stylelint command or ensure config file is available
# Same pattern as ESLint - run essential build steps only---
Category: Workspace / Monorepo
Workspace Package Not Found
pattern: "ENOENT.*workspace.*package.json|pnpm.*ERR_PNPM_NO_IMPORTER"
category: workspace
confidence: high
fix: |
# Ensure all workspace package.json files are copied
COPY packages ./packages
COPY e2e/package.json ./e2e/
COPY apps/desktop/src/main/package.json ./apps/desktop/src/main/Patches Directory Missing
pattern: "ENOENT.*patches/|Could not apply patch"
category: workspace
confidence: high
fix: |
# pnpm patches require patches directory
COPY patches ./patches.dockerignore Excludes Required Files
pattern: "COPY failed.*not found|failed to calculate checksum.*not found"
category: workspace
confidence: high
fix: |
# Check .dockerignore - likely excluding required workspace files
# Use specific exclusions instead of directory-level:
# Bad: e2e
# Good: e2e/*
# !e2e/package.json---
Category: Node.js Path / Runtime
Node Binary Not Found
pattern: "spawn /bin/node ENOENT|spawn node ENOENT|/bin/node.*not found"
category: runtime_path
confidence: high
fix: |
# node:slim images have node at /usr/local/bin/node, not /bin/node
# Some scripts hardcode /bin/node
RUN ln -sf /usr/local/bin/node /bin/nodeProxychains Not Found
pattern: "/bin/proxychains.*not found|proxychains.*ENOENT"
category: runtime_deps
confidence: high
fix: |
RUN apt-get update && apt-get install -y --no-install-recommends \
proxychains4 \
&& rm -rf /var/lib/apt/lists/*---
Category: Build-Time Environment
Build-Time Env Required (Next.js SSG)
pattern: "`(.+?)` is not set.*build|Failed to collect page data.*(.+?) is not set"
category: build_env
confidence: high
extract: variable name from capture group
fix: |
# Next.js SSG/ISR requires env vars at build time
# Add placeholder values for build stage:
ARG {VAR_NAME}_PLACEHOLDER="build-placeholder-value"
ENV {VAR_NAME}=${{{VAR_NAME}_PLACEHOLDER}}Next.js API Route SDK Initialization (Resend, Stripe, etc.)
pattern: "Missing API key.*Pass it to the constructor|error:.*API key|Failed to collect page data for /api/"
category: build_env
confidence: high
description: |
Next.js statically analyzes API routes during build time and attempts to load modules,
even if the code only runs at runtime. If an SDK is initialized at module top-level
(e.g., `const resend = new Resend(process.env.KEY)`), the build will fail due to
missing API key.
fix: |
# Add placeholder values in build stage (these won't be used at runtime)
ARG RESEND_API_KEY=re_placeholder_key
ARG STRIPE_SECRET_KEY=sk_placeholder_key
ARG NOTION_SECRET=placeholder_notion_secret
# ... add corresponding variables based on error message
ENV RESEND_API_KEY=${RESEND_API_KEY}
ENV STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
ENV NOTION_SECRET=${NOTION_SECRET}
detection: |
# Scan app/api/**/route.ts to detect required env vars:
grep -r "new.*process\.env\." app/api/
grep -r "process\.env\.\w\+" app/api/ | grep -v "process.env.NODE_ENV"Database URL Required at Build Time
pattern: "DATABASE_URL.*is not set|You are try to use database.*DATABASE_URL"
category: build_env
confidence: high
fix: |
# Some pages need DB access during build for static generation
ARG DATABASE_URL_PLACEHOLDER="postgres://placeholder:placeholder@localhost:5432/placeholder"
ENV DATABASE_URL=${DATABASE_URL_PLACEHOLDER}
ENV DATABASE_DRIVER=""Auth Secret Required at Build Time
pattern: "AUTH_SECRET.*is not set|KEY_VAULTS_SECRET.*is not set"
category: build_env
confidence: high
fix: |
ARG KEY_VAULTS_SECRET_PLACEHOLDER="build-placeholder-key-vaults-secret-32chars"
ENV KEY_VAULTS_SECRET=${KEY_VAULTS_SECRET_PLACEHOLDER}
ENV AUTH_SECRET=${KEY_VAULTS_SECRET_PLACEHOLDER}---
Category: Script/Entry Point
Build Script Missing
pattern: "Cannot find module.*prebuild|ERR_MODULE_NOT_FOUND.*scripts/"
category: script_missing
confidence: high
fix: |
# Build script excluded by .dockerignore
# Remove from .dockerignore or ensure COPY includes it
# Check if script path is in .dockerignore exclusionsServer Entry Point Not Found
pattern: "Cannot find module.*startServer|Cannot find module.*server.js"
category: script_missing
confidence: high
fix: |
# Copy server entry point in production stage
COPY --from=builder /app/scripts/serverLauncher/startServer.js ./startServer.js
COPY --from=builder /app/scripts/_shared ./scripts/_shared---
Category: Database Migration
Relation/Table Does Not Exist (Runtime)
pattern: "relation \"(.+?)\" does not exist|Table '(.+?)' doesn't exist|no such table: (.+)"
category: migration_failed
confidence: high
phase: runtime
extract: table name
fix: |
# CRITICAL: This is a RUNTIME error, indicating migrations never ran
# This should have been detected in analysis phase!
# Fix 1: Check if migration system was detected
# - Re-run analysis phase with migration detection
# - Verify migration_system.detected == true
# Fix 2: For Next.js Standalone + ORM
# Add separate ORM dependency installation:
RUN mkdir -p /deps && \
cd /deps && \
pnpm add pg drizzle-orm
COPY --from=build /deps/node_modules/drizzle-orm ./node_modules/drizzle-orm
COPY --from=build /deps/node_modules/pg ./node_modules/pg
# Fix 3: Verify entrypoint runs migrations
# docker-entrypoint.sh should have:
export MIGRATION_DB=1
# Or explicitly run migration command
# Fix 4: Fallback - Execute SQL files directly
# for file in migrations/*.sql; do
# psql $DATABASE_URL < $file
# done
prevention: |
This error should be caught in ANALYSIS phase by:
- Detecting migration directory (Step 12)
- Detecting ORM type
- Detecting standalone mode
- Warning if standalone + ORM without separate depsDrizzle/Prisma Module Not Found (Runtime)
pattern: "Cannot find module 'drizzle-orm'|Cannot find module '@prisma/client'|Cannot find module 'typeorm'"
category: migration_deps_missing
confidence: high
phase: runtime
extract: module name
fix: |
# CRITICAL: ORM not available in standalone mode
# Next.js standalone mode doesn't include all node_modules
# Must install ORM dependencies separately
# In build stage:
RUN mkdir -p /deps && \
cd /deps && \
pnpm add {module_name} pg
# In production stage:
COPY --from=build /deps/node_modules/{module_name} ./node_modules/{module_name}
COPY --from=build /deps/node_modules/pg ./node_modules/pg
prevention: |
Detect in analysis phase Step 12:
- migration_system.standalone_with_orm == true
- migration_system.requires_separate_deps == true
Then automatically use separate deps patternMigration Directory Not Found
pattern: "ENOENT.*migrations|Migration directory not found|no such file or directory.*migrations"
category: migration_files_missing
confidence: high
phase: build_or_runtime
fix: |
# Migration files not copied to image
# Add to Dockerfile production stage:
COPY --from=build /app/packages/database/migrations ./packages/database/migrations
# Or wherever migrations are located:
COPY --from=build /app/prisma/migrations ./prisma/migrations
COPY --from=build /app/drizzle ./drizzlebun/bunx Not Found in Migration Script
pattern: "sh: 1: bun: not found|sh: 1: bunx: not found|bun: command not found"
category: migration_runtime
confidence: high
phase: runtime
context: "Often in build-migrate-db or migration scripts"
fix: |
# Project uses bun for migrations but image only has node
# Option 1: Don't run migrations at build time
# Remove build-time migration from Dockerfile
# Run migrations at container startup instead
# Option 2: Install bun in image
RUN curl -fsSL https://bun.sh/install | bash
ENV PATH="/root/.bun/bin:$PATH"
# Option 3: Convert migration script to use node
# Replace 'bun run db:migrate' with 'npx drizzle-kit migrate'
prevention: |
In analysis phase, detect if migration scripts use bun
Recommend runtime migration instead of build-time---
Category: Build Memory Optimization
Build Script Contains Lint/Type-Check
pattern: "Linting|Type-checking|Running type check|eslint|tsc --noEmit"
category: build_optimization
confidence: medium
phase: build
detection: |
# During analysis, check package.json build script:
BUILD_SCRIPT=$(jq -r '.scripts.build' package.json)
if echo "$BUILD_SCRIPT" | grep -qE "lint|type-check"; then
# Heavy operations detected
fi
fix: |
# These are CI tasks, not essential for Docker image
# Skip them in Docker build to save memory and time
# Instead of:
RUN npm run build # Includes lint, type-check, tests
# Use:
RUN npx next build # Only build the app
# Or if custom prebuild needed:
RUN npx tsx scripts/prebuild.mts && npx next build
# Comment in Dockerfile:
# NOTE: Skipping lint/type-check in Docker build
# These should be run in CI/CD pipeline
prevention: |
In analysis phase Step 13:
- Parse build script for heavy operations
- Recommend optimized build command
- Set appropriate memory limitSitemap Generation Fails or Slows Build
pattern: "buildSitemapIndex|sitemap.*failed|Cannot find module.*sitemap"
category: build_optimization
confidence: high
phase: build
fix: |
# Sitemap generation often not needed in Docker deployment
# Skip it to reduce build time and complexity
# Remove from build script or add flag to skip:
ENV SKIP_SITEMAP=1
# Or modify package.json script to check env var
prevention: |
In analysis phase Step 13:
- Detect sitemap generation in build script
- Recommend skipping for Docker builds---
Category: Custom CLI / Monorepo Build
Wrong Build Command for Custom CLI
pattern: "ENOENT.*assets-manifest\\.json|build output not found|Missing static files"
category: custom_cli
confidence: high
phase: runtime
context: "Build succeeded but outputs missing - likely wrong build command"
fix: |
# CRITICAL: Monorepo may use custom CLI instead of yarn workspace
# Detection: Check package.json scripts for custom CLI usage
# Wrong (standard workspace):
# RUN yarn workspace @scope/web build
# Correct (use detected CLI syntax from analysis):
# Turborepo: yarn turbo run build --filter=@scope/web
# Nx: yarn nx build web
# Lerna: yarn lerna run build --scope=@scope/web
# Custom: yarn ${CLI_NAME} build -p @scope/web
prevention: |
In analysis phase Step 14:
- Detect custom CLI in package.json scripts
- Use correct CLI syntax for all build commands
- Never assume `yarn workspace <pkg> build` worksGit Hash Required
pattern: "Failed to open git repo|nodegit.*ENOENT|Repository.*not found|git.*rev-parse.*failed"
category: custom_cli
confidence: high
phase: build
fix: |
# Build tools may require git hash for versioning
# In Docker, .git is not available
# Set environment variable to bypass git requirement
ENV GITHUB_SHA=docker-build
# Or use build arg for custom value:
ARG GIT_COMMIT=docker-build
ENV GITHUB_SHA=${GIT_COMMIT}
prevention: |
In analysis phase Step 14:
- grep for "GITHUB_SHA|Repository|rev-parse" in build tools
- Automatically add ENV GITHUB_SHA if detectedCLI Init/Config File Missing
pattern: "init.*failed|prettier.*not found|Cannot read config file|eslint.*not found|tsconfig.*not found"
category: custom_cli
confidence: medium
phase: build
fix: |
# Custom CLI may depend on config files excluded by .dockerignore
# Common required config files (check .dockerignore):
# - .prettierrc / .prettierignore (code formatting)
# - .eslintrc.* / oxlint.json (linting)
# - tsconfig.json (TypeScript)
# - .editorconfig (editor settings)
# Fix: Comment out exclusions in .dockerignore
# Example:
# .prettierignore <- Comment this out
# .prettierrc <- Comment this out
prevention: |
In analysis phase Step 14:
- Detect which config files CLI depends on
- Ensure they are NOT in .dockerignoreStatic Assets Path Mismatch
pattern: "ENOENT.*static.*manifest|webAssets.*not found|Cannot find static directory"
category: custom_cli
confidence: high
phase: runtime
fix: |
# Backend code hardcodes expected static asset paths
# Must copy frontend outputs to correct locations
# Example (paths detected from analysis):
COPY --from=builder /app/${FRONTEND_OUTPUT} ./${BACKEND_EXPECTS}
# e.g., COPY --from=builder /app/packages/web/dist ./static
# Detection: Search backend code for static path references
# grep -rE "projectRoot.*static|assets-manifest|webAssets" packages/backend/ src/server/
prevention: |
In analysis phase Step 14:
- Detect backend's expected static paths
- Map frontend output paths to expected locations
- Generate correct COPY commands in Dockerfile---
Category: Rust/Native Module Build
Rust Toolchain Missing
pattern: "cargo.*not found|rustc.*not found|error: no default toolchain configured"
category: native_module
confidence: high
phase: build
fix: |
# Install Rust toolchain in build stage
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stableWrong Rust Target
pattern: "error: linking with.*failed|unknown target triple|can't find crate"
category: native_module
confidence: high
phase: build
fix: |
# Must use correct target for container architecture
ARG TARGETARCH
RUN if [ "$TARGETARCH" = "arm64" ]; then \
rustup target add aarch64-unknown-linux-gnu && \
yarn workspace @pkg/native build --target aarch64-unknown-linux-gnu; \
else \
rustup target add x86_64-unknown-linux-gnu && \
yarn workspace @pkg/native build --target x86_64-unknown-linux-gnu; \
fiNAPI-RS Build Dependencies Missing
pattern: "napi.*build failed|tree-sitter.*error|clang.*not found"
category: native_module
confidence: high
phase: build
fix: |
# NAPI-RS and tree-sitter require clang
RUN apt-get update && apt-get install -y --no-install-recommends \
clang \
llvm \
pkg-config \
libssl-dev \
&& rm -rf /var/lib/apt/lists/*
# Set clang for compatibility
ENV CC="clang -D_BSD_SOURCE" \
TARGET_CC="clang -D_BSD_SOURCE"---
Unknown Error Fallback
If no pattern matches:
1. Log the full error message 2. Check if error contains a file path → might be COPY issue or .dockerignore issue 3. Check if error contains package name → might be dependency issue 4. Check if error mentions env var → might need build-time placeholder 5. Check if error mentions workspace → might be missing workspace files 6. Return to user with error for manual review
Debugging Checklist
When build fails with unknown error:
1. File not found: Check .dockerignore, ensure file is not excluded 2. Module not found: Check if it's a workspace package that wasn't copied 3. Env var not set: Add ARG/ENV placeholder for build time 4. Permission denied: Check USER directive placement 5. Command not found: Check if binary exists in the image (node path, proxychains, etc.)
Lessons Learned from Real Projects
This document captures patterns and solutions from actual Dockerfile generation experiences to prevent repeated mistakes.
---
Case Study: LobeChat (L3 Complexity)
Project: LobeChat - Next.js 16 + React 19 + pnpm workspace (39+ packages) Date: 2026-02-05 Iterations: 10+ (TOO MANY - should be reduced to 3-5 with improvements) Final Status: Successful after implementing all fixes
Timeline of Issues
Issue 1: TypeScript Project Reference
- When: Build iteration 1
- Error:
File '/app/apps/desktop' not found - Root Cause:
.dockerignoreexcludedapps/desktop/tsconfig.json - Fix: Added
!apps/desktop/tsconfig.jsonto .dockerignore - Prevention: Generate.md now includes validation checklist for .dockerignore
- Status: Pattern added to skill
Issue 2: Memory Exhaustion (OOM Killed)
- When: Build iteration 2
- Error: Exit code 137,
JavaScript heap out of memory - Root Cause:
npm run buildincluded lint/type-check consuming 12GB+ memory - Fix: Changed build command to skip CI tasks:
RUN npx tsx scripts/prebuild.mts && npx next build --webpack
ENV NODE_OPTIONS="--max-old-space-size=8192"- Prevention: Analysis phase Step 13 now detects heavy operations
- Status: Pattern added to skill
Issue 3: Sitemap Build Failure
- When: Build iteration 3
- Error:
Cannot find module '/app/scripts/buildSitemapIndex/index.ts' - Root Cause: Sitemap scripts excluded by .dockerignore
- Fix: Removed sitemap generation (not essential for Docker)
- Prevention: Analysis phase now detects sitemap generation
- Status: Pattern added to skill
Issue 4: bun Command Not Found
- When: Build iteration 4
- Error:
sh: 1: bun: not foundduringbuild-migrate-db - Root Cause: Migration script used
bun run db:migrate - Fix: Removed build-time DB migration, moved to runtime
- Prevention: Analysis phase now detects runtime tool requirements
- Status: Pattern added to skill
Issue 5: Database Migrations Not Running
- When: After deployment (runtime)
- Error:
relation "users" does not exist - Root Cause: Migrations never executed - Standalone mode doesn't include drizzle-orm
- Investigation Steps:
1. Entrypoint attempted to run migrations with MIGRATION_DB=1 2. Standalone output missing drizzle-orm dependencies 3. Migration script failed silently 4. User reported app not working
- Fix: Manually executed all 76 SQL files into PostgreSQL
- Prevention: Analysis phase Step 12 now detects migration systems
- Long-term Fix: Generate Dockerfile with separate ORM deps:
# Build stage - install ORM separately
RUN mkdir -p /deps && cd /deps && pnpm add pg drizzle-orm
# Production stage - copy ORM deps
COPY --from=build /deps/node_modules/drizzle-orm ./node_modules/drizzle-orm
COPY --from=build /deps/node_modules/pg ./node_modules/pg- Status: Pattern added to skill
Issue 6: Runtime Validation Missing
- When: After build completed
- Problem: Declared success after
docker buildpassed, but app didn't work - Root Cause: No runtime validation phase
- Fix: Added comprehensive runtime validation:
- Verify container starts
- Check database tables exist
- Test HTTP endpoints
- Scan logs for errors
- Prevention: Phase 4 Runtime Validation added
- Status: Pattern added to skill
User Feedback
"The entire process should not require human interaction"
Lesson: Skill must be FULLY automated - auto-generate all config files including secrets.
"Image build should not depend on real environment variables"
Lesson: Use placeholders at build time, inject real values at runtime.
"It took about 10+ iterations, too many"
Lesson: Most issues should be detected in ANALYSIS phase, not discovered during build/runtime.
"The database migration issue should have been detected during code analysis phase, not after build completion when users tried to use it"
Lesson: Migration detection is CRITICAL and must happen in analysis phase.
---
Patterns to Converge into Skill
Priority 1: Critical (Prevents Runtime Failures)
1.1 Migration System Detection (Analysis Phase)
Implementation: modules/analyze.md - Step 12
Detection:
- Check for migration directories (packages/*/migrations, prisma/migrations, etc.)
- Detect ORM type (Drizzle, Prisma, TypeORM)
- Count migration files
- Check if standalone mode + ORM (critical pattern)
- Verify migration execution method (build-time, runtime, none)
Warning Triggers:
- Migration files found BUT no execution method → CRITICAL
- Standalone mode + ORM without separate deps → CRITICAL
- Unknown ORM with migrations → WARNING
Benefit: Prevents "relation does not exist" failures at runtime
Status: Implemented in modules/analyze.md1.2 Runtime Validation Phase
Implementation: modules/build-fix.md - Post-Build Validation
Validation Steps:
1. Container startup check
2. Database migration verification (psql -c "\dt")
3. Migration count validation
4. HTTP endpoint testing (200/302/401 OK, 500 FAIL)
5. Log error scanning
Success Criteria:
- Only declare success if ALL validations pass
- Don't stop at docker build success
Benefit: Catches migration failures before user discovers them
Status: Implemented in modules/build-fix.md1.3 Standalone Mode + ORM Pattern
Implementation: modules/generate.md - Migration Handling
Pattern Detection:
- Next.js output: 'standalone' + ORM detected
Solution:
- Install ORM deps separately in /deps
- Copy to final image alongside standalone output
- Include migration files
- Create proper entrypoint script
Example: LobeChat pattern from official Dockerfile
Benefit: Prevents silent migration failures in standalone mode
Status: Implemented in modules/generate.mdPriority 2: High (Prevents Build Failures)
2.1 Build Script Complexity Analysis
Implementation: modules/analyze.md - Step 13
Detection:
- Parse package.json build script
- Identify heavy operations (lint, type-check, test, sitemap)
- Count workspace packages (memory multiplier)
- Calculate memory risk
Recommendation:
- Workspace 39+ packages + lint/type-check = HIGH RISK
- Suggest optimized build command (skip CI tasks)
- Set appropriate NODE_OPTIONS memory limit
Benefit: Prevents OOM failures (Exit 137)
Status: Implemented in modules/analyze.md2.2 Build Optimization in Generation
Implementation: modules/generate.md - Build Optimization Handling
Application:
- Use optimized build command from analysis
- Add comments explaining why operations skipped
- Set memory limits based on complexity
Example:
# Skipping lint/type-check (run in CI, not Docker)
RUN npx tsx scripts/prebuild.mts && npx next build
ENV NODE_OPTIONS="--max-old-space-size=8192"
Benefit: Reduces build time and prevents OOM
Status: Implemented in modules/generate.mdPriority 3: Medium (Improves User Experience)
3.1 Complete Automation
Implementation: modules/generate.md - Output Files
Auto-Generate:
- .env.docker.local with test secrets (32+ char random)
- docker-entrypoint.sh with migration logic
- DOCKER.md with deployment guide
- All supporting documentation
Zero User Input:
- User should NEVER need to create files manually
- User should NEVER need to generate secrets
- docker-compose up -d should work immediately
Benefit: Achieves "zero human interaction" requirement
Status: Should be implemented in next iteration3.2 Environment Variable Patterns
Implementation: modules/analyze.md + modules/generate.md
Principle:
- Build-time: Use placeholders that pass validation
- Runtime: Inject real values via docker run -e or compose
Detection:
- Scan for required env vars
- Check minimum length requirements
- Generate valid placeholders automatically
Example:
ARG KEY_VAULTS_SECRET="build-placeholder-32chars-long-xxxxx"
# Real value at runtime: docker run -e KEY_VAULTS_SECRET="real-key"
Benefit: Build never depends on real secrets
Status: Already implementedPriority 4: Low (Project-Specific Optimizations)
These are NOT converged into skill as they're too specific:
4.1 Image Size Optimization
- Using FROM scratch (distroless)
- Multi-architecture builds
- Compression techniques
- Reason: Too project-specific, official images vary widely
4.2 Regional Optimizations
- China mirror support (USE_CN_MIRROR)
- Regional CDN configuration
- Reason: Regional requirements, not universal
4.3 Proxychains/VPN Support
- Proxychains4 installation
- Proxy configuration
- Reason: Specific use case, not common enough
---
Error Pattern Enhancements
New Patterns Added
Database Migration Errors (Runtime)
Pattern: "relation \"(.+?)\" does not exist"
Category: migration_failed
Phase: runtime
Fix: Check migration system, install ORM deps separately if standalone
Prevention: Detect in analysis phase Step 12
Added to: knowledge/error-patterns.mdORM Module Not Found (Runtime)
Pattern: "Cannot find module 'drizzle-orm'"
Category: migration_deps_missing
Phase: runtime
Fix: Install ORM separately, copy to final image
Prevention: Detect standalone + ORM in analysis phase
Added to: knowledge/error-patterns.mdBuild Memory Issues
Pattern: "Exit code: 137|Killed|heap out of memory"
Category: memory
Phase: build
Fix: Skip heavy operations, increase memory limit
Prevention: Detect in analysis phase Step 13
Added to: knowledge/error-patterns.md---
Metrics & Success Criteria
Before Improvements
- LobeChat Build: 10+ iterations
- Success Declaration: After docker build passes
- User Discovered Issues: Database not working
- Human Interaction: Required for .env.docker.local
After Improvements (Target)
- Iterations: 3-5 maximum (even for L3 complexity)
- Success Declaration: After runtime validation passes
- User Discovered Issues: None (caught in validation)
- Human Interaction: Zero (fully automated)
Validation Metrics
Detection Rate (Analysis Phase):
- Migration systems: 100% (was 0%)
- Heavy build operations: 100% (was 0%)
- ORM dependencies: 100% (was 0%)
Prevention Rate (Generation Phase):
- OOM failures: 90%+ (was ~30%)
- Migration failures: 95%+ (was 0%)
- Runtime errors: 90%+ (was ~50%)
Success Rate (Runtime Validation):
- Database tables created: Required check
- Application responding: Required check
- No silent failures: Verified before success declaration---
Implementation Status
Completed
1. Analysis Phase Step 12: Migration Detection 2. Analysis Phase Step 13: Build Complexity Analysis 3. Generate Phase: Migration Handling (Standalone + ORM pattern) 4. Generate Phase: Build Optimization 5. Build-Fix Phase: Runtime Validation 6. Error Patterns: Migration-related errors 7. Error Patterns: Build memory issues 8. SKILL.md: Updated workflow and capabilities
In Progress
1. Complete test coverage for new detection modules 2. Documentation examples for each pattern
Future Enhancements
1. Auto-generate .env.docker.local with proper random secrets 2. Auto-generate DOCKER.md with project-specific content 3. Enhanced validation reporting (structured JSON output) 4. Support for more ORMs (currently Drizzle/Prisma/TypeORM)
---
Summary
Key Learnings
1. Detect Early: Most issues should be found in ANALYSIS phase, not BUILD or RUNTIME 2. Validate Completely: Don't declare success until runtime validation passes 3. Automate Everything: Zero human interaction is the goal 4. Migration Critical: Database migrations are #1 cause of runtime failures 5. Build Optimization: Heavy CI tasks cause OOM in Docker builds 6. Custom CLI Detection: Monorepos often use custom CLIs - using wrong command = silent failures
Impact
By converging these patterns into the skill:
- Reduced Iterations: From 10+ to 3-5 for similar complexity
- Earlier Detection: Issues found in analysis, not runtime
- Better Validation: No silent failures
- User Experience: Zero manual steps required
Next Project Benefits
When the skill encounters a similar project (L3 complexity with migrations): 1. Analysis phase detects migrations → warns about standalone + ORM 2. Generation phase uses separate deps pattern automatically 3. Runtime validation catches any migration failures 4. User gets working app on first try (or max 3-5 iterations)
This is convergence: Learning from experience to prevent repetition.
---
Case Study: AFFiNE (L3 Complexity - Monorepo with Custom CLI)
Project: AFFiNE - Knowledge base with frontend (Next.js) + backend (NestJS) + Rust native modules Date: 2026-02-06 Iterations: 5+ (reduced from initial failures) Final Status: Successful after implementing all fixes
Project Characteristics
- Monorepo: pnpm workspace with 50+ packages
- Frontend: React + Next.js (web, mobile, admin apps)
- Backend: NestJS server with Prisma ORM
- Native Module: Rust/NAPI-RS for performance-critical operations
- Custom CLI:
yarn affine- custom build tool, NOT standard workspace commands - External Services: PostgreSQL (pgvector), Redis, ManticoreSearch
Timeline of Issues
Issue 1: Wrong Build Command (CRITICAL)
- When: Initial Dockerfile generation
- Error:
ENOENT: no such file or directory, open '/app/static/assets-manifest.json' - Root Cause: Used
yarn workspace @affine/web buildinstead ofyarn affine build -p @affine/web - Investigation:
1. Official AFFiNE CI/CD uses yarn affine build -p <package> 2. Custom CLI defined in tools/cli/bin/cli.js 3. Individual packages' build scripts invoke the CLI internally
- Fix: Changed all build commands to use affine CLI syntax:
RUN yarn affine build -p @affine/web && \
yarn affine build -p @affine/mobile && \
yarn affine build -p @affine/admin
RUN yarn affine build -p @affine/server- Prevention: Added Step 14 (Custom CLI Detection) to analyze.md
- Status: Pattern added to skill
Issue 2: Git Hash Dependency
- When: After fixing build command
- Error:
Failed to open git repo: could not find repository at '/app/' - Root Cause: Build tool uses
nodegitto get commit hash for versioning, but.gitnot in Docker context - Detection: Found in
tools/cli/src/webpack/html-plugin.ts:
const gitShortHash = once(() => {
const { GITHUB_SHA } = process.env;
if (GITHUB_SHA) {
return GITHUB_SHA.substring(0, 9);
}
const repo = new Repository(ProjectRoot.value); // Fails without .git
});- Fix: Set environment variable to bypass git requirement:
ENV GITHUB_SHA=docker-build- Prevention: Added git hash detection to Step 14
- Status: Pattern added to skill
Issue 3: Configuration Files in .dockerignore
- When: During dependency installation
- Error:
affine initfailed silently, causing build issues - Root Cause:
.prettierrcand.prettierignorewere in.dockerignorebut needed by CLI - Investigation: The
affine initscript (run in postinstall) requires these files for code generation - Fix: Removed these files from .dockerignore:
# Keep prettier config (needed for affine init)
# .prettierignore
# .prettierrc- Prevention: Added config file dependency detection to Step 14
- Status: Pattern added to skill
Issue 4: Static Assets Path Mapping
- When: After frontend build succeeded but server failed at runtime
- Error:
ENOENT: no such file or directory, open '/app/static/assets-manifest.json' - Root Cause: Backend expects frontend builds at
/app/static/but they were built to different paths - Investigation: Found in backend code:
this.webAssets = this.readHtmlAssets(join(env.projectRoot, 'static'));- Fix: Copy frontend outputs to expected locations:
COPY --from=app-builder /app/packages/frontend/apps/web/dist ./static
COPY --from=app-builder /app/packages/frontend/apps/mobile/dist ./static/mobile
COPY --from=app-builder /app/packages/frontend/admin/dist ./static/admin- Prevention: Added static assets path detection to Step 14
- Status: Pattern added to skill
Issue 5: Rust Native Module Multi-Architecture
- When: Building for different CPU architectures
- Challenge: Must build for both x86_64 and arm64
- Solution: Auto-detect architecture and set appropriate Rust target:
ARG TARGETARCH
RUN if [ "$TARGETARCH" = "arm64" ]; then \
rustup target add aarch64-unknown-linux-gnu; \
yarn workspace @affine/server-native build --target aarch64-unknown-linux-gnu; \
else \
rustup target add x86_64-unknown-linux-gnu; \
yarn workspace @affine/server-native build --target x86_64-unknown-linux-gnu; \
fi- Prevention: Added Step 15 (Native Module Detection) to analyze.md
- Status: Pattern added to skill
Key Differences from Official Approach
| Aspect | Official AFFiNE | Self-Hosted Docker |
|---|---|---|
| Build Location | GitHub Actions CI | Inside Docker container |
| Artifacts | Pre-built, uploaded | Built from source |
| Native Module | Pre-compiled binaries | Compiled during build |
| Dependencies | Minimal runtime | Full build toolchain |
| Image Size | ~500MB | ~2GB (includes build deps) |
Generalized Lessons (Applicable to ALL Monorepo Projects)
1. Detect Build System First: Never assume standard yarn workspace <pkg> build works. Always detect what CLI/build tool the project uses. 2. Git Hash Bypass: Many build tools require git hash. Set the appropriate env var (commonly GITHUB_SHA, GIT_COMMIT, etc.) in Docker builds. 3. Config File Dependencies: CLI tools often depend on formatter/linter configs. Detect and ensure they're not in .dockerignore. 4. Static Asset Mapping: Backend code may hardcode expected paths. Detect and map frontend outputs correctly. 5. Multi-Stage for Native Modules: If project has Rust/C++ native modules, use separate build stage for caching.
User Feedback
"From this experience, is there anything that can be converged into the skill so similar projects succeed on first try?"
Lesson: Custom CLI detection is CRITICAL for monorepo projects. Must be detected in analysis phase.
---
Consolidated Patterns for Monorepo Projects
Detection Checklist (Analysis Phase)
monorepo_detection:
# Step 1: Detect workspace type
workspace_type: pnpm | yarn | npm | turborepo | nx | lerna
# Step 2: Detect custom CLI (CRITICAL)
custom_cli:
detected: true | false
name: "${DETECTED_CLI_NAME}" # e.g., turbo, nx, custom name
type: "standard | custom"
entry: "${CLI_ENTRY_PATH}" # e.g., tools/cli/bin/cli.js
build_syntax: "${BUILD_COMMAND}" # Detected build command pattern
# Step 3: Detect CLI dependencies
cli_dependencies:
git_hash_required: true | false
git_hash_env: "${ENV_VAR_NAME}" # e.g., GITHUB_SHA, GIT_COMMIT
config_files: [] # Files that CLI depends on
postinstall_script: "${SCRIPT}" # If postinstall runs init
# Step 4: Detect native modules
native_modules:
rust_required: true | false
packages: [] # List of native package paths
multi_arch: true | false
# Step 5: Detect static asset paths
static_assets:
backend_expects: "${PATH}" # Where backend looks for static files
frontend_outputs: [] # Where frontend builds output toGeneration Rules (Generate Phase)
# Rule 1: Use detected CLI for builds (NEVER assume standard workspace commands)
# Use: analysis.custom_cli.build_syntax
RUN ${analysis.custom_cli.build_syntax}
# Rule 2: Set git hash bypass if required
# Only add if: analysis.custom_cli.dependencies.git_hash_required == true
ENV ${GIT_HASH_ENV}=docker-build
# Rule 3: Keep config files (modify .dockerignore)
# Remove from .dockerignore: analysis.custom_cli.dependencies.config_files
# Rule 4: Multi-stage for native modules if detected
# Only add if: analysis.native_modules.rust_required == true
FROM builder AS native-builder
RUN yarn workspace ${NATIVE_PACKAGE} build --target ${RUST_TARGET}
# Rule 5: Map static assets correctly
# For each frontend_output, create COPY to backend_expects
COPY --from=builder /app/${frontend_output} ./${backend_expects}External Services Detection
external_services:
# Database detection
database:
postgres:
detection: "DATABASE_URL|POSTGRES_|prisma|drizzle|typeorm"
image: "postgres:16-alpine"
image_vector: "pgvector/pgvector:pg16" # Use if vector search detected
healthcheck: "pg_isready -U ${USER}"
mysql:
detection: "MYSQL_|mysql:|mysql2"
image: "mysql:8.0"
mongodb:
detection: "MONGODB_|mongodb:|mongoose"
image: "mongo:7"
# Cache/Queue
redis:
detection: "REDIS_|ioredis|bull|bullmq"
image: "redis:7-alpine"
healthcheck: "redis-cli ping"
# Search engines
search:
elasticsearch:
detection: "ELASTIC_|elasticsearch|@elastic"
image: "elasticsearch:8.11.0"
meilisearch:
detection: "MEILI_|meilisearch"
image: "getmeili/meilisearch:v1.5"
manticore:
detection: "MANTICORE|manticoresearch"
image: "manticoresearch/manticore:latest"
# Object storage
s3:
detection: "S3_|MINIO_|@aws-sdk/client-s3"
image: "minio/minio:latest"
command: "server /data --console-address :9001"---
Updated Metrics After AFFiNE Case
Detection Success Rate
| Pattern | Before AFFiNE | After AFFiNE |
|---|---|---|
| Custom CLI | 0% | 95%+ |
| Git Hash Dependency | 0% | 95%+ |
| Config File Dependencies | 50% | 90%+ |
| Static Asset Mapping | 30% | 85%+ |
| Native Module (Rust) | 60% | 90%+ |
Iteration Reduction
- Complex Monorepo (AFFiNE-style): From 10+ to 3-5 iterations
- Key Factor: Detecting custom CLI in analysis phase eliminates 50%+ of build failures
Monorepo Custom CLI Patterns
Overview
Many large monorepo projects have custom CLI tools for building instead of standard yarn workspace commands. Failing to detect and use these CLIs is a common cause of build failures.
Key Principle: NEVER assume yarn workspace <pkg> build works. Always detect the actual build system.
Detection Checklist
Step 1: Detect Known Monorepo Tools
# Check for well-known monorepo CLIs
KNOWN_CLIS=(
"turbo" # Turborepo
"nx" # Nx
"lerna" # Lerna
"rush" # Rush
)
for cli in "${KNOWN_CLIS[@]}"; do
if [ -f "node_modules/.bin/$cli" ] || grep -q "\"$cli\"" package.json; then
echo "Detected: $cli"
CLI_TYPE="standard"
CLI_NAME="$cli"
break
fi
done
# Check for turbo.json (Turborepo indicator)
[ -f "turbo.json" ] && CLI_NAME="turbo"
# Check for nx.json (Nx indicator)
[ -f "nx.json" ] && CLI_NAME="nx"
# Check for lerna.json (Lerna indicator)
[ -f "lerna.json" ] && CLI_NAME="lerna"Step 2: Detect Custom CLI
# If no standard CLI found, check for custom CLI in package.json scripts
# Look for scripts that define a single-word command
CUSTOM_CLI=$(jq -r '
.scripts | to_entries[] |
select(.key | test("^[a-z]+$")) |
select(.value | test("^[a-z]+ |^r ")) |
.key
' package.json 2>/dev/null | head -1)
# Check tools/ directory for CLI definitions
for dir in "tools/cli" "tools/scripts" "scripts/cli"; do
if [ -d "$dir" ]; then
CLI_ENTRY=$(find "$dir" -name "*.js" -o -name "*.ts" | head -1)
[ -n "$CLI_ENTRY" ] && CLI_TYPE="custom"
fi
doneStep 3: Determine Build Syntax
Each CLI has different syntax. Detection must determine the correct pattern:
| CLI | Build Syntax | Filter Flag |
|---|---|---|
| Turborepo | yarn turbo run build --filter=<pkg> | --filter= |
| Nx | yarn nx build <project> | positional |
| Lerna | yarn lerna run build --scope=<pkg> | --scope= |
| Rush | rush build -t <pkg> | -t |
| Custom | varies | detect from source |
# Determine syntax based on CLI type
case "$CLI_NAME" in
turbo) BUILD_SYNTAX="yarn turbo run build --filter=\${PACKAGE}" ;;
nx) BUILD_SYNTAX="yarn nx build \${PROJECT}" ;;
lerna) BUILD_SYNTAX="yarn lerna run build --scope=\${PACKAGE}" ;;
rush) BUILD_SYNTAX="rush build -t \${PACKAGE}" ;;
*)
# Custom CLI - analyze source for flag patterns
if grep -rqE "\-p.*package|--package" tools/ 2>/dev/null; then
BUILD_SYNTAX="yarn $CLI_NAME build -p \${PACKAGE}"
elif grep -rqE "\-\-filter" tools/ 2>/dev/null; then
BUILD_SYNTAX="yarn $CLI_NAME build --filter=\${PACKAGE}"
else
BUILD_SYNTAX="yarn $CLI_NAME build \${PACKAGE}"
fi
;;
esacCommon CLI Patterns
Pattern 1: Turborepo
Detection:
[ -f "turbo.json" ] && echo "Turborepo detected"Correct Build:
RUN yarn turbo run build --filter=@scope/web
RUN yarn turbo run build --filter=@scope/serverPattern 2: Nx
Detection:
[ -f "nx.json" ] && echo "Nx detected"Correct Build:
RUN yarn nx build web
RUN yarn nx build serverPattern 3: Lerna
Detection:
[ -f "lerna.json" ] && echo "Lerna detected"Correct Build:
RUN yarn lerna run build --scope=@scope/webPattern 4: Custom CLI
Detection:
# Custom CLI often defined in tools/ or has special script in package.json
if [ -d "tools/cli" ]; then
echo "Custom CLI detected"
fiAnalysis Required: 1. Find CLI entry point 2. Analyze source for argument parsing 3. Determine correct syntax
Example Build:
# Syntax varies by project - must be detected
RUN yarn ${CLI_NAME} build -p @scope/webGit Hash Dependency
Many build tools require git commit hash for versioning.
Detection:
# Common patterns for git hash usage
if grep -rqE "GITHUB_SHA|GIT_COMMIT|GIT_SHA|COMMIT_HASH|rev-parse|nodegit|simple-git" tools/ src/ scripts/ 2>/dev/null; then
echo "Git hash required"
# Find the specific env var name
GIT_ENV=$(grep -rohE "(GITHUB_SHA|GIT_COMMIT|GIT_SHA|COMMIT_HASH)" tools/ src/ scripts/ 2>/dev/null | sort -u | head -1)
fiSolution:
# Set environment variable to bypass git requirement
# Use the detected env var name, or default to GITHUB_SHA
ENV ${GIT_ENV:-GITHUB_SHA}=docker-buildConfiguration File Dependencies
Custom CLIs often depend on configuration files.
Common Required Files:
.prettierrc/.prettierignore- Code formatting.eslintrc.*/oxlint.json- Lintingtsconfig.json- TypeScript config.editorconfig- Editor settings
Detection:
CONFIG_DEPS=()
# Check what the CLI/build system references
for config in ".prettierrc" ".prettierignore" ".eslintrc.js" "oxlint.json" "tsconfig.json"; do
if [ -f "$config" ] && grep -rqE "${config#.}" tools/ scripts/ 2>/dev/null; then
CONFIG_DEPS+=("$config")
fi
doneFix .dockerignore:
# Comment out any config files that CLI needs:
# .prettierrc
# .prettierignore
# tsconfig.jsonpostinstall Script Handling
Many monorepos run initialization in postinstall.
Detection:
POSTINSTALL=$(jq -r '.scripts.postinstall // ""' package.json)
if [ -n "$POSTINSTALL" ]; then
echo "postinstall script: $POSTINSTALL"
fiOptions:
1. Keep postinstall (recommended):
RUN yarn install --immutable --inline-builds
# postinstall runs automatically2. Skip postinstall, run manually:
RUN yarn install --immutable --ignore-scripts
RUN yarn ${CLI_NAME} init # or equivalentNative Module (Rust/NAPI-RS) Builds
Detection:
# Check for Rust project
HAS_RUST=false
if [ -f "Cargo.toml" ] || ls packages/*/Cargo.toml 2>/dev/null; then
HAS_RUST=true
fi
# Check for NAPI-RS
if grep -qE "@napi-rs|napi-derive" package.json Cargo.toml 2>/dev/null; then
HAS_NAPI_RS=true
fiBuild Pattern:
# Install Rust toolchain
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/usr/local/cargo/bin:$PATH"
# Install NAPI-RS build dependencies
RUN apt-get update && apt-get install -y clang llvm
# Build with correct target (auto-detect architecture)
ARG TARGETARCH
RUN if [ "$TARGETARCH" = "arm64" ]; then \
rustup target add aarch64-unknown-linux-gnu && \
yarn workspace @scope/native build --target aarch64-unknown-linux-gnu; \
else \
rustup target add x86_64-unknown-linux-gnu && \
yarn workspace @scope/native build --target x86_64-unknown-linux-gnu; \
fiStatic Assets Path Detection
Backend servers often expect frontend builds at specific paths.
Detection:
# Search for static path references in backend code
STATIC_PATH=""
if grep -rqE "static|public" packages/backend/ src/server/ 2>/dev/null; then
STATIC_PATH=$(grep -rohE "(static|public)" packages/backend/ src/server/ 2>/dev/null | head -1)
fi
# Find frontend output directories
FRONTEND_OUTPUTS=$(find packages apps -name "dist" -type d 2>/dev/null)Solution:
# Copy frontend builds to where backend expects
# Paths detected from analysis
COPY --from=builder /app/${FRONTEND_OUTPUT} ./${BACKEND_EXPECTS}Analysis Output Format
When analyzing a monorepo, produce this structured output:
custom_cli:
detected: true | false
name: "${CLI_NAME}" # Detected CLI name
type: "standard | custom" # Known tool or custom
entry: "${CLI_ENTRY}" # Path to CLI (if custom)
build_syntax: "${BUILD_SYNTAX}" # Complete build command template
packages_to_build:
- name: "@scope/web"
command: "yarn ${CLI_NAME} build ..."
output: "packages/web/dist"
dependencies:
git_hash:
required: true | false
env_var: "${GIT_ENV}" # e.g., GITHUB_SHA
fallback: "docker-build"
config_files: [] # Files NOT to exclude in .dockerignore
postinstall:
script: "${POSTINSTALL}"
recommendation: "keep | skip_and_run_manually"
native_modules:
rust: true | false
packages: []
multi_arch: true | false
static_assets:
backend_expects: "${STATIC_PATH}"
frontend_outputs: []Troubleshooting
"command not found: ${CLI_NAME}"
Cause: CLI not installed or PATH not set.
Fix:
# Ensure dependencies installed first
RUN yarn install --immutable
# CLI available via yarn
RUN yarn ${CLI_NAME} build ..."Unknown Syntax Error" / "Invalid argument"
Cause: Wrong CLI syntax.
Fix: Verify syntax by checking CLI help or source code.
"Failed to open git repo"
Cause: Build requires git hash but .git not in Docker context.
Fix:
ENV ${GIT_ENV}=docker-build"assets-manifest.json not found" or similar static file errors
Cause: Frontend output not copied to correct location.
Fix: 1. Verify frontend builds successfully 2. Check where backend expects static files 3. Add correct COPY command in Dockerfile
System Dependencies Mapping
NPM Packages → System Libraries
Image Processing
| Package | Debian/Ubuntu Packages |
|---|---|
sharp | libvips-dev |
canvas | build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev |
@napi-rs/canvas | build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev |
jimp | (none - pure JS) |
gm | graphicsmagick |
imagemagick | imagemagick |
Database Clients (Native)
| Package | Debian/Ubuntu Packages |
|---|---|
better-sqlite3 | python3 make g++ |
sqlite3 | python3 make g++ |
pg-native | libpq-dev |
Cryptography
| Package | Debian/Ubuntu Packages |
|---|---|
bcrypt | python3 make g++ |
argon2 | python3 make g++ |
sodium-native | python3 make g++ |
General Native Addons
| Package | Debian/Ubuntu Packages |
|---|---|
Any with node-gyp | python3 make g++ |
@swc/* | (prebuilt binaries, usually none) |
esbuild | (prebuilt binaries, usually none) |
PDF/Document
| Package | Debian/Ubuntu Packages |
|---|---|
pdf-lib | (none - pure JS) |
pdfkit | (none - pure JS) |
puppeteer | chromium-browser (or use puppeteer with bundled chromium) |
playwright | (use playwright install) |
---
Python Packages → System Libraries
Database
| Package | Debian/Ubuntu Packages |
|---|---|
psycopg2 | libpq-dev |
psycopg2-binary | (none - uses bundled libs) |
mysqlclient | default-libmysqlclient-dev |
pymssql | freetds-dev |
Image Processing
| Package | Debian/Ubuntu Packages |
|---|---|
Pillow | libjpeg-dev libpng-dev libtiff-dev libwebp-dev |
opencv-python | libgl1-mesa-glx libglib2.0-0 |
opencv-python-headless | libgl1-mesa-glx libglib2.0-0 |
Cryptography
| Package | Debian/Ubuntu Packages |
|---|---|
cryptography | libssl-dev libffi-dev |
pynacl | libsodium-dev |
XML/HTML
| Package | Debian/Ubuntu Packages |
|---|---|
lxml | libxml2-dev libxslt-dev |
beautifulsoup4 | (none - pure Python) |
Scientific
| Package | Debian/Ubuntu Packages |
|---|---|
numpy | (prebuilt wheels, usually none) |
scipy | libopenblas-dev (optional, for building from source) |
pandas | (prebuilt wheels, usually none) |
ML/AI
| Package | Debian/Ubuntu Packages |
|---|---|
torch | (prebuilt wheels) |
tensorflow | (prebuilt wheels) |
onnxruntime | (prebuilt wheels) |
---
Go Packages → System Libraries
Go typically compiles to static binaries, but some packages require CGO:
| Package | Debian/Ubuntu Packages |
|---|---|
go-sqlite3 | gcc (for CGO build) |
| Most packages | (none - static compilation) |
For CGO-enabled builds:
# Build stage needs:
RUN apk add --no-cache gcc musl-dev # Alpine
RUN apt-get install -y gcc # DebianFor static builds (recommended):
RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o main .---
Java → System Libraries
Most dependencies are handled by Maven/Gradle. Rare cases:
| Use Case | Debian/Ubuntu Packages |
|---|---|
| Native image (GraalVM) | build-essential zlib1g-dev |
| Fonts for PDF | fontconfig fonts-dejavu |
---
Detection Script
Use this to detect which system packages are needed:
#!/bin/bash
# Scan package.json for known native dependencies
NATIVE_DEPS=""
if grep -q '"sharp"' package.json; then
NATIVE_DEPS="$NATIVE_DEPS libvips-dev"
fi
if grep -q '"canvas"\|"@napi-rs/canvas"' package.json; then
NATIVE_DEPS="$NATIVE_DEPS build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev"
fi
if grep -q '"better-sqlite3"\|"bcrypt"\|"argon2"' package.json; then
NATIVE_DEPS="$NATIVE_DEPS python3 make g++"
fi
if grep -q '"pg-native"' package.json; then
NATIVE_DEPS="$NATIVE_DEPS libpq-dev"
fi
echo "Required system packages: $NATIVE_DEPS"---
Alpine vs Debian/Slim
When to use Debian Slim (Recommended)
- Projects with native dependencies (sharp, canvas, bcrypt)
- Next.js projects
- Complex Node.js applications
When to use Alpine
- Simple Go applications (static binary)
- Minimal Python scripts without native deps
- Size is critical and no native deps
Package Name Differences
| Debian | Alpine |
|---|---|
python3 | python3 |
make | make |
g++ | g++ |
build-essential | build-base |
libvips-dev | vips-dev |
libcairo2-dev | cairo-dev |
libpango1.0-dev | pango-dev |
libpq-dev | postgresql-dev |
Module: Project Analysis
Purpose
Analyze a project to extract all information needed for Dockerfile generation.
Execution Steps
Pre-loaded Context (Optional)
When invoked from the sealos-deploy pipeline, Phase 1 analysis results may already be available (loaded from .sealos/analysis.json).
If analysis.json data is available, skip overlapping steps and use pre-loaded values:
| Skip Step | Use pre-loaded value |
|---|---|
| Step 1 (language/framework) | language, framework |
| Step 2 (package manager) | package_manager |
| Step 4 (port) | port |
| Step 5 (databases) | databases — only skip DB type detection; still detect S3, search engines, message queues |
| Step 7 (Docker config) | has_dockerfile — still check docker-compose.yml and .dockerignore |
| Step 8 (monorepo) | complexity_tier — if L1/L2, skip; if L3, still enumerate workspace packages |
Always run (Dockerfile-specific, not in analysis.json): Steps 3, 6, 9, 10, 11, 12, 13, 14, 15, 16, 17
If no pre-loaded data (standalone /dockerfile): run all 17 steps as normal.
Step 1: Detect Language and Framework
Check for these files in order:
package.json → Node.js (check for next/nuxt/express/koa/nest)
requirements.txt → Python (check for django/flask/fastapi)
pyproject.toml → Python (modern)
go.mod → Go
pom.xml → Java (Maven)
build.gradle → Java (Gradle)
Cargo.toml → Rust
composer.json → PHP
Gemfile → RubyFor Node.js, additionally check:
next.config.*→ Next.jsnuxt.config.*→ Nuxtnest-cli.json→ NestJS
Step 2: Detect Package Manager
package-lock.json → npm
yarn.lock → yarn
pnpm-lock.yaml → pnpm
bun.lockb → bunStep 3: Extract Build and Run Commands
Node.js: Read package.json scripts:
{
"scripts": {
"build": "...", // Build command
"start": "...", // Production run command
"dev": "..." // Development (ignore)
}
}Python: Check for:
Makefilewith build targetssetup.py/pyproject.tomlentry points- Common patterns:
uvicorn,gunicorn,python app.py
Go: Standard go build → binary execution
Step 4: Detect Port
Search patterns in source code:
# Node.js
grep -rE "listen\s*\(\s*[0-9]+" --include="*.js" --include="*.ts"
grep -rE "PORT.*[0-9]+" --include="*.js" --include="*.ts"
# Python
grep -rE "uvicorn.*port|flask.*port|run\(.*port" --include="*.py"
# Go
grep -rE "ListenAndServe.*:" --include="*.go"Common defaults:
- Express/Koa/Nest: 3000
- Next.js: 3000
- FastAPI/Django: 8000
- Go: 8080
- Spring Boot: 8080
Step 5: Detect External Services
Purpose: Auto-detect required external services for docker-compose generation.
Detection Method:
# ============================================
# Database Detection
# ============================================
# PostgreSQL
if grep -rqE "DATABASE_URL|POSTGRES_|postgres:|pg_|prisma|drizzle|typeorm" . 2>/dev/null; then
DB_TYPE="postgres"
# Check for vector extension requirement
if grep -rqE "pgvector|vector.*embedding|createIndex.*vector" . 2>/dev/null; then
DB_IMAGE="pgvector/pgvector:pg16"
else
DB_IMAGE="postgres:16-alpine"
fi
fi
# MySQL
if grep -rqE "MYSQL_|mysql:|mysql2" . 2>/dev/null; then
DB_TYPE="mysql"
DB_IMAGE="mysql:8.0"
fi
# MongoDB
if grep -rqE "MONGODB_|mongodb:|mongoose" . 2>/dev/null; then
DB_TYPE="mongodb"
DB_IMAGE="mongo:7"
fi
# ============================================
# Cache/Queue Detection
# ============================================
# Redis
if grep -rqE "REDIS_|ioredis|redis:|bull|bullmq|@upstash/redis" . 2>/dev/null; then
REDIS_REQUIRED=true
REDIS_IMAGE="redis:7-alpine"
fi
# RabbitMQ
if grep -rqE "RABBITMQ_|amqp:|amqplib" . 2>/dev/null; then
RABBITMQ_REQUIRED=true
RABBITMQ_IMAGE="rabbitmq:3-management-alpine"
fi
# ============================================
# Object Storage Detection
# ============================================
# S3/MinIO
if grep -rqE "S3_|MINIO_|@aws-sdk/client-s3|aws-sdk.*S3" . 2>/dev/null; then
S3_REQUIRED=true
# Default to MinIO for self-hosted
S3_IMAGE="minio/minio:latest"
fi
# ============================================
# Search Engine Detection
# ============================================
# Elasticsearch
if grep -rqE "ELASTIC_|elasticsearch|@elastic/elasticsearch" . 2>/dev/null; then
SEARCH_TYPE="elasticsearch"
SEARCH_IMAGE="elasticsearch:8.11.0"
fi
# Meilisearch
if grep -rqE "MEILI_|meilisearch" . 2>/dev/null; then
SEARCH_TYPE="meilisearch"
SEARCH_IMAGE="getmeili/meilisearch:v1.5"
fi
# ManticoreSearch
if grep -rqE "MANTICORE|manticoresearch|INDEXER_SEARCH_PROVIDER.*manticore" . 2>/dev/null; then
SEARCH_TYPE="manticore"
SEARCH_IMAGE="manticoresearch/manticore:latest"
fiOutput:
external_services:
database:
type: "${DB_TYPE}" # postgres | mysql | mongodb | none
image: "${DB_IMAGE}" # Recommended Docker image
env_var: "DATABASE_URL" # Primary connection env var
has_vector: true | false # If pgvector needed
redis:
required: ${REDIS_REQUIRED}
image: "${REDIS_IMAGE}"
env_var: "REDIS_URL"
s3:
required: ${S3_REQUIRED}
image: "${S3_IMAGE}"
provider: "minio | aws | custom"
env_vars:
- "S3_ENDPOINT"
- "S3_ACCESS_KEY"
- "S3_SECRET_KEY"
search:
type: "${SEARCH_TYPE}" # elasticsearch | meilisearch | manticore | none
image: "${SEARCH_IMAGE}"
env_var: "${SEARCH_ENV_VAR}"
message_queue:
type: "rabbitmq | kafka | none"
image: "${MQ_IMAGE}"docker-compose Generation Rules:
- Only include services that were detected
- Use detected image variants (e.g., pgvector vs postgres)
- Set appropriate health checks for each service
- Configure proper networking between services
- Generate environment variable templates
Step 6: Detect System Library Requirements
Check package.json dependencies for known native modules:
| NPM Package | Requires |
|---|---|
| sharp | libvips-dev |
| canvas / @napi-rs/canvas | build-essential, libcairo2-dev, libpango1.0-dev |
| better-sqlite3 | python3, make, g++ |
| bcrypt | python3, make, g++ |
| node-gyp (any) | python3, make, g++ |
Check requirements.txt for Python:
| Pip Package | Requires |
|---|---|
| psycopg2 | libpq-dev |
| Pillow | libjpeg-dev, libpng-dev |
| cryptography | libssl-dev, libffi-dev |
| lxml | libxml2-dev, libxslt-dev |
Step 7: Check for Existing Docker Configuration
Look for:
Dockerfile/Dockerfile.*docker-compose.yml/docker-compose.yaml.dockerignore
If found, extract key decisions for reference (DO NOT blindly copy).
Step 8: Detect Workspace/Monorepo Configuration
For pnpm workspaces:
# Check for pnpm-workspace.yaml
if [ -f "pnpm-workspace.yaml" ]; then
# Parse workspace packages
grep -E "^\s*-\s+" pnpm-workspace.yaml
fi
# Check for patches directory (pnpm patch feature)
if [ -d "patches" ]; then
PATCHES_DIR="patches"
fiFor npm/yarn workspaces:
# Check package.json workspaces field
grep -A 20 '"workspaces"' package.jsonFor Turborepo:
# Check for turbo.json
if [ -f "turbo.json" ]; then
MONOREPO_TYPE="turborepo"
fiOutput for workspace analysis:
workspace:
enabled: true
type: "pnpm" # pnpm | npm | yarn | turborepo | nx
config_file: "pnpm-workspace.yaml"
packages:
- "packages/**"
- "apps/desktop/src/main"
- "e2e"
patches_dir: "patches" # pnpm patched dependencies
required_files: # Files that MUST be copied for workspace to work
- "pnpm-workspace.yaml"
- "patches/**"
- "packages/*/package.json"
- "apps/desktop/src/main/package.json"
- "e2e/package.json"Step 9: Detect Package Manager Configuration
Check .npmrc / .yarnrc.yml:
# Check for lockfile=false (common in some projects)
if grep -q "lockfile=false" .npmrc 2>/dev/null; then
LOCKFILE_DISABLED=true
fi
# Check for other important settings
grep -E "^(resolution-mode|public-hoist-pattern|shamefully-hoist)" .npmrcOutput:
package_manager_config:
lockfile_disabled: true # If lockfile=false in .npmrc
config_file: ".npmrc"
special_settings:
- "lockfile=false"
- "resolution-mode=highest"Step 10: Detect Build-Time Environment Variables
Scan for build-time required env vars:
# Check for env validation in config files
grep -rE "process\.env\.\w+" src/ --include="*.ts" --include="*.js" | \
grep -E "(throw|required|must be set)" | \
grep -oE "process\.env\.\w+" | sort -u
# Check for @t3-oss/env-nextjs or similar
grep -rE "createEnv|z\.string\(\)" src/env* 2>/dev/nullCommon build-time required vars for Next.js:
KEY_VAULTS_SECRET- Database encryptionDATABASE_URL- For static page generation with DB accessAUTH_SECRET- Authentication
Output:
build_time_env:
required:
- name: KEY_VAULTS_SECRET
source: "src/libs/server-config/db.ts"
placeholder: "build-placeholder-32chars"
- name: DATABASE_URL
source: "src/libs/server-config/db.ts"
placeholder: "postgres://placeholder:placeholder@localhost:5432/placeholder"
optional:
- name: NEXT_PUBLIC_API_URL
default: "http://localhost:3000"Step 11: Detect Custom Scripts and Entry Points
Check for docker-specific scripts:
# Look for docker-specific build scripts
grep -E '"build:docker"|"start:docker"|"docker"' package.json
# Check for custom server entry points
ls -la scripts/serverLauncher/*.js 2>/dev/null
ls -la scripts/*/startServer.js 2>/dev/nullOutput:
custom_scripts:
build: "npm run build:docker" # Prefer docker-specific if exists
start: "node startServer.js"
entry_point: "scripts/serverLauncher/startServer.js"
migrations: "scripts/migrateServerDB/docker.cjs"Step 12: Detect Database Migration System
Purpose: Critical to detect migrations BEFORE Dockerfile generation to prevent runtime failures.
Detection Checklist:
# 1. Check for migration directories
MIGRATION_DIRS=(
"packages/database/migrations"
"prisma/migrations"
"drizzle"
"migrations"
"db/migrations"
"database/migrations"
)
for dir in "${MIGRATION_DIRS[@]}"; do
if [ -d "$dir" ]; then
MIGRATION_DIR="$dir"
MIGRATION_COUNT=$(find "$dir" -name "*.sql" -o -name "*.ts" -o -name "*.js" | wc -l)
break
fi
done
# 2. Detect ORM type
if [ -f "prisma/schema.prisma" ]; then
ORM="prisma"
MIGRATION_CMD="npx prisma migrate deploy"
elif [ -f "drizzle.config.ts" ]; then
ORM="drizzle"
MIGRATION_CMD="npx drizzle-kit migrate"
elif grep -q "typeorm" package.json; then
ORM="typeorm"
MIGRATION_CMD="npx typeorm migration:run"
else
ORM="unknown"
fi
# 3. Check if migrations run at build time or runtime
if grep -q "build-migrate" package.json; then
MIGRATION_TIME="build"
elif grep -q "MIGRATION_DB" .env.example 2>/dev/null; then
MIGRATION_TIME="runtime"
MIGRATION_ENV_VAR="MIGRATION_DB=1"
else
MIGRATION_TIME="none" # CRITICAL WARNING
fi
# 4. Check for Next.js Standalone mode + ORM combination
if grep -q "output.*standalone" next.config.* 2>/dev/null && [ "$ORM" != "unknown" ]; then
# CRITICAL: Standalone mode doesn't include all node_modules
# ORM dependencies must be installed separately
STANDALONE_WITH_ORM=true
fiOutput:
migration_system:
detected: true
orm: "drizzle" # prisma | drizzle | typeorm | unknown
migration_dir: "packages/database/migrations"
migration_count: 76
migration_files:
- "0000_init.sql"
- "0049_better_auth.sql"
- "...76 files total"
execution_timing: "runtime" # build | runtime | none
execution_command: "npx drizzle-kit migrate"
execution_env_var: "MIGRATION_DB=1"
# Critical pattern detection
standalone_with_orm: true
requires_separate_deps: true # If true, must install ORM separately
warnings:
- "Next.js standalone mode + ORM detected - ORM must be installed separately"
- "76 migration files found - ensure they run before first request"
- "No automatic migration detected - must add runtime migration"Warning Conditions:
migration_count > 0ANDmigration_time == "none"→ CRITICAL: Migrations will never runstandalone_with_orm == trueANDrequires_separate_deps == false→ Migrations will fail silentlyorm == "unknown"ANDmigration_count > 0→ Cannot determine migration method
Step 13: Analyze Build Script Complexity
Purpose: Detect memory-intensive or unnecessary build steps to prevent OOM failures.
Detection Method:
# 1. Parse build script from package.json
BUILD_SCRIPT=$(jq -r '.scripts.build' package.json)
# 2. Check for heavy operations
HEAVY_OPS=()
if echo "$BUILD_SCRIPT" | grep -qE "lint|eslint"; then
HEAVY_OPS+=("lint")
fi
if echo "$BUILD_SCRIPT" | grep -qE "type-check|tsc.*--noEmit"; then
HEAVY_OPS+=("type-check")
fi
if echo "$BUILD_SCRIPT" | grep -qE "test|jest|vitest"; then
HEAVY_OPS+=("test")
fi
if echo "$BUILD_SCRIPT" | grep -qE "sitemap|buildSitemap"; then
HEAVY_OPS+=("sitemap")
fi
# 3. Check workspace package count (memory multiplier)
if [ -f "pnpm-workspace.yaml" ]; then
WORKSPACE_COUNT=$(grep -E "^\s*-\s+" pnpm-workspace.yaml | wc -l)
if [ "$WORKSPACE_COUNT" -gt 20 ]; then
MEMORY_RISK="high"
elif [ "$WORKSPACE_COUNT" -gt 10 ]; then
MEMORY_RISK="medium"
fi
fiOutput:
build_complexity:
build_script: "npm run prebuild && next build"
heavy_operations:
- name: "lint"
location: "prebuild"
essential: false
memory_usage: "2-4GB"
recommendation: "Skip in Docker build (run in CI)"
- name: "type-check"
location: "prebuild"
essential: false
memory_usage: "4-8GB"
recommendation: "Skip in Docker build (run in CI)"
- name: "sitemap"
location: "build"
essential: false
memory_usage: "500MB"
recommendation: "Skip in Docker (not needed for container)"
workspace_package_count: 39
memory_risk: "high" # low | medium | high
recommendations:
optimized_build: "npx tsx scripts/prebuild.mts && npx next build --webpack"
memory_limit: "NODE_OPTIONS=--max-old-space-size=8192"
rationale: "Skip lint/type-check/sitemap to reduce memory from 12GB+ to 4GB"Step 14: Detect Custom CLI Tools
Purpose: Many monorepos use custom CLI tools instead of standard workspace commands. Using the wrong build command is a common cause of failure.
Why This Matters:
- Standard
yarn workspace @pkg buildmay not work - Custom CLI may require specific flags/syntax
- Build outputs may go to non-standard locations
- CLI may depend on git hash, config files, or initialization scripts
Detection Method:
# Step 1: Detect well-known monorepo CLIs
KNOWN_CLIS=("turbo" "nx" "lerna" "rush")
for cli in "${KNOWN_CLIS[@]}"; do
if [ -f "node_modules/.bin/$cli" ] || grep -q "\"$cli\"" package.json; then
CLI_NAME="$cli"
CLI_TYPE="standard"
break
fi
done
# Step 2: Detect custom CLI in root package.json scripts
# Look for scripts that invoke a single command (potential CLI)
CUSTOM_CLI=$(jq -r '.scripts | to_entries[] | select(.key | test("^[a-z]+$")) | select(.value | test("^[a-z]+ ")) | .key' package.json 2>/dev/null | head -1)
if [ -n "$CUSTOM_CLI" ] && [ "$CLI_TYPE" != "standard" ]; then
CLI_NAME="$CUSTOM_CLI"
CLI_TYPE="custom"
fi
# Step 3: Check for CLI definition in tools/ or scripts/
for dir in "tools/cli" "tools/scripts" "scripts/cli"; do
if [ -d "$dir" ]; then
CLI_ENTRY=$(find "$dir" -name "*.js" -o -name "*.ts" | head -1)
if [ -n "$CLI_ENTRY" ]; then
CLI_TYPE="custom"
break
fi
fi
done
# Step 4: Analyze how packages are built
# Check if individual packages use CLI internally
for pkg_json in packages/*/package.json apps/*/package.json; do
if [ -f "$pkg_json" ]; then
BUILD_CMD=$(jq -r '.scripts.build // ""' "$pkg_json")
if [ -n "$BUILD_CMD" ] && ! echo "$BUILD_CMD" | grep -qE "^(tsc|webpack|next|vite|esbuild)"; then
# Non-standard build command, might use custom CLI
CUSTOM_BUILD_DETECTED=true
fi
fi
done
# Step 5: Determine build syntax by examining usage patterns
if [ "$CLI_TYPE" = "standard" ]; then
case "$CLI_NAME" in
turbo) BUILD_SYNTAX="yarn turbo run build --filter=\${PACKAGE}" ;;
nx) BUILD_SYNTAX="yarn nx build \${PROJECT}" ;;
lerna) BUILD_SYNTAX="yarn lerna run build --scope=\${PACKAGE}" ;;
rush) BUILD_SYNTAX="rush build -t \${PACKAGE}" ;;
esac
elif [ "$CLI_TYPE" = "custom" ]; then
# Analyze CLI source or README to determine syntax
# Common patterns: -p for package, --filter, positional argument
if grep -rqE "\-p.*package|--package" tools/ 2>/dev/null; then
BUILD_SYNTAX="yarn $CLI_NAME build -p \${PACKAGE}"
else
BUILD_SYNTAX="yarn $CLI_NAME build \${PACKAGE}"
fi
fiGit Hash Dependency Detection:
# Check if build requires git hash
GIT_HASH_REQUIRED=false
GIT_HASH_ENV=""
# Common patterns for git hash usage
if grep -rqE "GITHUB_SHA|GIT_COMMIT|GIT_SHA|COMMIT_HASH" tools/ src/ scripts/ 2>/dev/null; then
GIT_HASH_REQUIRED=true
GIT_HASH_ENV=$(grep -rohE "(GITHUB_SHA|GIT_COMMIT|GIT_SHA|COMMIT_HASH)" tools/ src/ scripts/ 2>/dev/null | sort -u | head -1)
fi
# Check for nodegit, simple-git, or git command usage
if grep -rqE "nodegit|simple-git|Repository.*open|git.*rev-parse" tools/ src/ 2>/dev/null; then
GIT_HASH_REQUIRED=true
[ -z "$GIT_HASH_ENV" ] && GIT_HASH_ENV="GITHUB_SHA"
fiConfiguration File Dependencies:
# Detect which config files the CLI/build system depends on
CONFIG_DEPS=()
# Check for prettier dependency
if grep -rqE "prettier|\.prettierrc" tools/ scripts/ 2>/dev/null; then
[ -f ".prettierrc" ] && CONFIG_DEPS+=(".prettierrc")
[ -f ".prettierignore" ] && CONFIG_DEPS+=(".prettierignore")
fi
# Check for eslint/oxlint dependency
if grep -rqE "eslint|oxlint" tools/ scripts/ 2>/dev/null; then
[ -f ".eslintrc.js" ] && CONFIG_DEPS+=(".eslintrc.js")
[ -f "oxlint.json" ] && CONFIG_DEPS+=("oxlint.json")
fi
# Check for tsconfig dependency (almost always needed)
if grep -rqE "tsconfig|typescript" tools/ scripts/ 2>/dev/null; then
[ -f "tsconfig.json" ] && CONFIG_DEPS+=("tsconfig.json")
fi
# Check postinstall script for init commands
POSTINSTALL=$(jq -r '.scripts.postinstall // ""' package.json)
if [ -n "$POSTINSTALL" ]; then
POSTINSTALL_RUNS_INIT=true
fiStatic Assets Path Detection:
# Detect where backend expects static files
BACKEND_STATIC_PATH=""
FRONTEND_OUTPUTS=()
# Search for static path references in backend code
STATIC_REFS=$(grep -rohE "(static|public|dist|assets).*manifest|readHtmlAssets|webAssets" packages/backend/ src/server/ 2>/dev/null)
if [ -n "$STATIC_REFS" ]; then
BACKEND_STATIC_PATH=$(echo "$STATIC_REFS" | grep -oE "(static|public)" | head -1)
fi
# Detect frontend build output directories
for frontend_dir in packages/frontend/*/dist apps/*/dist packages/*/.next; do
if [ -d "$frontend_dir" ] 2>/dev/null || grep -q "output.*dist" "$(dirname $frontend_dir)/package.json" 2>/dev/null; then
FRONTEND_OUTPUTS+=("$frontend_dir")
fi
doneOutput:
custom_cli:
detected: true
name: "${CLI_NAME}" # Detected CLI name
type: "${CLI_TYPE}" # standard | custom
entry: "${CLI_ENTRY}" # Path to CLI entry point (if custom)
build_syntax: "${BUILD_SYNTAX}" # Complete build command template
# The ${PACKAGE} placeholder should be replaced with actual package names
packages_to_build: # Detected packages that need building
- name: "@scope/web"
build_cmd: "yarn ${CLI_NAME} build -p @scope/web"
output_dir: "packages/web/dist"
- name: "@scope/server"
build_cmd: "yarn ${CLI_NAME} build -p @scope/server"
output_dir: "packages/server/dist"
dependencies:
git_hash_required: ${GIT_HASH_REQUIRED}
git_hash_env: "${GIT_HASH_ENV}" # e.g., GITHUB_SHA, GIT_COMMIT
git_hash_fallback: "docker-build"
config_files: ${CONFIG_DEPS} # Files that must NOT be in .dockerignore
# e.g., [".prettierrc", ".prettierignore", "tsconfig.json"]
postinstall_runs_init: ${POSTINSTALL_RUNS_INIT}
static_assets:
backend_expects: "${BACKEND_STATIC_PATH}" # e.g., "static", "public"
frontend_outputs: ${FRONTEND_OUTPUTS} # Source paths to copy from
# Generation phase will create COPY commands to map outputs to expected pathsWarning Conditions:
custom_cli.detected == trueAND analysis usesyarn workspace→ CRITICAL: Wrong build commandgit_hash_required == trueAND git_hash_env not set in Dockerfile → Build will failconfig_filesitems found in.dockerignore→ CLI init will failfrontend_outputs!=backend_expects→ Runtime ENOENT errors
Key Principle: The goal is to DETECT the patterns, not hardcode specific project names. The detection should work for ANY monorepo by analyzing: 1. What CLI is being used (by checking scripts, dependencies, and file structure) 2. What syntax that CLI requires (by analyzing CLI source or documentation) 3. What dependencies the build system has (git hash, config files, etc.) 4. Where outputs are generated and expected (static asset mapping)
Step 15: Detect Rust/Native Module Requirements
Purpose: Some projects include Rust native modules that require special build setup.
Detection Method:
# 1. Check for Cargo files
if [ -f "Cargo.toml" ] || ls packages/*/Cargo.toml 2>/dev/null; then
HAS_RUST=true
fi
# 2. Check for napi-rs dependencies
if grep -qE "@napi-rs|napi-derive" package.json Cargo.toml 2>/dev/null; then
HAS_NAPI_RS=true
fi
# 3. Detect native package location
NATIVE_PACKAGES=$(find packages -name "Cargo.toml" -exec dirname {} \;)Output:
native_modules:
rust_required: true
napi_rs: true
packages:
- path: "packages/backend/native"
name: "@scope/native"
build_command: "yarn workspace @scope/native build"
targets:
- "x86_64-unknown-linux-gnu"
- "aarch64-unknown-linux-gnu"
build_dependencies:
- clang
- llvm
- pkg-config
- libssl-devStep 16: Validate Build Command Dependencies
Purpose: Detect build commands that will fail due to missing config files in Docker context, and automatically determine the resolved build command.
Core Principle: If a config file is in .dockerignore, the user intentionally excluded it. Respect that decision by skipping commands that depend on it.
Detection Method:
# Command → Required config files mapping
declare -A CMD_CONFIG_DEPS=(
["lint"]=".eslintrc.js .eslintrc.json .eslintrc.cjs eslint.config.js eslint.config.mjs"
["eslint"]=".eslintrc.js .eslintrc.json .eslintrc.cjs eslint.config.js eslint.config.mjs"
["type-check"]="tsconfig.json"
["tsc --noEmit"]="tsconfig.json"
["stylelint"]=".stylelintrc .stylelintrc.js .stylelintrc.json .stylelintrc.cjs"
["prettier --check"]=".prettierrc .prettierrc.js .prettierrc.json .prettierrc.cjs"
["jest"]="jest.config.js jest.config.ts jest.config.mjs"
["vitest"]="vitest.config.ts vitest.config.js vitest.config.mts"
)
# Parse build-related scripts
PREBUILD=$(jq -r '.scripts.prebuild // ""' package.json)
BUILD=$(jq -r '.scripts.build // ""' package.json)
# Split script by && or ; into individual commands
# For each command:
# 1. Check if it matches any key in CMD_CONFIG_DEPS
# 2. If yes, find which config file it needs
# 3. Check if config file exists in project
# 4. Check if config file is excluded in .dockerignore
# 5. Determine action: keep or skip
resolve_command() {
local script="$1"
local resolved=""
# Split by &&
IFS='&&' read -ra COMMANDS <<< "$script"
for cmd in "${COMMANDS[@]}"; do
cmd=$(echo "$cmd" | xargs) # trim whitespace
should_skip=false
skip_reason=""
for pattern in "${!CMD_CONFIG_DEPS[@]}"; do
if echo "$cmd" | grep -q "$pattern"; then
config_files="${CMD_CONFIG_DEPS[$pattern]}"
# Check if any required config exists
config_found=""
for cfg in $config_files; do
if [ -f "$cfg" ]; then
config_found="$cfg"
break
fi
done
if [ -z "$config_found" ]; then
# Config file doesn't exist
should_skip=true
skip_reason="Config file not found"
elif [ -f ".dockerignore" ] && grep -qE "^${config_found}$|^${config_found%.*}\." .dockerignore; then
# Config file excluded in .dockerignore
should_skip=true
skip_reason="Config file excluded in .dockerignore"
fi
break
fi
done
if [ "$should_skip" = false ]; then
if [ -n "$resolved" ]; then
resolved="$resolved && $cmd"
else
resolved="$cmd"
fi
fi
done
echo "$resolved"
}Decision Logic:
For each command in build script:
│
├── Command needs config file?
│ │
│ ├── NO → Keep command
│ │
│ └── YES → Config file exists?
│ │
│ ├── NO → Skip command (config doesn't exist)
│ │
│ └── YES → Config in .dockerignore?
│ │
│ ├── YES → Skip command (user excluded it)
│ │
│ └── NO → Keep commandOutput:
build_command_resolution:
original_prebuild: "tsx scripts/prebuild.mts && npm run lint"
original_build: "cross-env NODE_OPTIONS=... next build"
commands:
- cmd: "tsx scripts/prebuild.mts"
requires_config: "scripts/prebuild.mts"
config_status: "available"
action: "keep"
- cmd: "npm run lint"
requires_config: ".eslintrc.js"
config_status: "excluded_in_dockerignore"
action: "skip"
skip_reason: "Config file .eslintrc.js excluded in .dockerignore"
# Final resolved command for Dockerfile
resolved_prebuild: "tsx scripts/prebuild.mts"
resolved_build: "next build --webpack"
# Full docker build command
docker_build_command: "npx tsx scripts/prebuild.mts && npx cross-env NODE_OPTIONS=--max-old-space-size=8192 npx next build --webpack"
skipped_commands:
- command: "npm run lint"
reason: "Config file .eslintrc.js excluded in .dockerignore"Key Rules: 1. Respect .dockerignore: If user excluded a config file, skip commands that need it 2. No user interaction: Automatically determine which commands to skip 3. Document skipped commands: Record what was skipped and why for Dockerfile comments 4. Prefix with npx: Use npx for CLI tools to ensure they're found in Docker
Step 17: Determine Complexity Level
L1 (Simple):
- Single language
- No build step OR simple build (just
npm install) - No external service dependencies
- No workspace
- No migrations
- Examples: Express API, simple Python script
L2 (Medium):
- Has build step (Next.js, TypeScript compilation, etc.)
- Has external services (Database, Redis)
- May have environment variable requirements at build time
- Simple migration system
- Examples: Next.js app, Django with PostgreSQL
L3 (Complex):
- Monorepo structure (pnpm-workspace, turborepo, nx)
- Multi-language (Python backend + Node frontend)
- Complex build pipeline
- Many external dependencies
- Has build-time env var requirements
- Custom entry points / server launchers
- Complex migration system (76+ migrations, ORM dependencies)
- Examples: Large monorepo projects, enterprise applications
Output Format
analysis:
language: "typescript"
framework: "nextjs"
framework_version: "16.x"
package_manager: "pnpm"
package_manager_version: "10.20.0"
# Package manager configuration
package_manager_config:
lockfile_disabled: true # lockfile=false in .npmrc
config_file: ".npmrc"
install_command: "pnpm install" # NOT --frozen-lockfile if lockfile disabled
# Workspace / Monorepo configuration
workspace:
enabled: true
type: "pnpm"
config_file: "pnpm-workspace.yaml"
packages:
- "packages/**"
- "apps/desktop/src/main"
- "e2e"
patches_dir: "patches"
required_copy_files: # Files MUST be copied for workspace
- path: "pnpm-workspace.yaml"
dest: "./"
- path: "patches"
dest: "./patches"
- path: "packages"
dest: "./packages"
- path: "e2e/package.json"
dest: "./e2e/"
- path: "apps/desktop/src/main/package.json"
dest: "./apps/desktop/src/main/"
# Build configuration
build:
command: "npm run build:docker" # Prefer docker-specific script
fallback_command: "npm run build"
output_dir: ".next"
standalone_mode: true # output: 'standalone' in next.config
env_required: # Build-time required env vars
- name: KEY_VAULTS_SECRET
placeholder: "build-placeholder-32chars"
- name: DATABASE_URL
placeholder: "postgres://placeholder:placeholder@localhost:5432/placeholder"
- name: AUTH_SECRET
placeholder: "build-placeholder-auth-secret"
# Runtime configuration
run:
command: "node startServer.js"
entry_point: "scripts/serverLauncher/startServer.js"
port: 3210
env_required:
- DATABASE_URL
- KEY_VAULTS_SECRET
- AUTH_SECRET
# External dependencies
dependencies:
external_services:
- type: postgres
env_var: DATABASE_URL
recommended_image: "pgvector/pgvector:pg16"
- type: redis
env_var: REDIS_URL
optional: true
- type: s3
env_var: S3_ENDPOINT
optional: true
system_libs:
- name: sharp
packages: ["libvips-dev"]
- name: "@napi-rs/canvas"
packages: ["build-essential", "libcairo2-dev", "libpango1.0-dev"]
# Files that must NOT be excluded from docker build context
required_files:
- ".npmrc" # Package manager config
- "pnpm-workspace.yaml"
- "patches/**"
- "scripts/prebuild.mts"
- "scripts/serverLauncher/**"
- "scripts/migrateServerDB/**"
- "scripts/_shared/**"
- "packages/database/migrations/**"
# Existing docker configuration analysis
existing_docker:
has_dockerfile: false
has_compose: false
key_decisions: []
# Database migration system
migration_system:
detected: true
orm: "drizzle"
migration_dir: "packages/database/migrations"
migration_count: 76
execution_timing: "runtime"
execution_env_var: "MIGRATION_DB=1"
standalone_with_orm: true
requires_separate_deps: true
warnings:
- "Next.js standalone mode + Drizzle ORM - must install drizzle-orm separately"
- "76 migration files - ensure runtime execution before first request"
# Build complexity analysis
build_complexity:
build_script: "npm run prebuild && next build"
heavy_operations:
- name: "lint"
essential: false
recommendation: "Skip in Docker (run in CI)"
- name: "type-check"
essential: false
recommendation: "Skip in Docker (run in CI)"
workspace_package_count: 39
memory_risk: "high"
optimized_build: "npx tsx scripts/prebuild.mts && npx next build --webpack"
memory_limit: "NODE_OPTIONS=--max-old-space-size=8192"
# Build command resolution (from Step 16)
build_command_resolution:
original_prebuild: "tsx scripts/prebuild.mts && npm run lint"
original_build: "cross-env NODE_OPTIONS=... next build"
resolved_prebuild: "tsx scripts/prebuild.mts"
resolved_build: "next build --webpack"
docker_build_command: "npx tsx scripts/prebuild.mts && npx cross-env NODE_OPTIONS=--max-old-space-size=8192 npx next build --webpack"
skipped_commands:
- command: "npm run lint"
reason: "Config file .eslintrc.js excluded in .dockerignore"
# Complexity assessment
complexity: "L3"
max_iterations: 5
# Warnings / Notes
warnings:
- "Project uses lockfile=false - cannot use --frozen-lockfile"
- "Workspace packages must be copied individually for Docker cache optimization"
- "Build requires placeholder env vars for Next.js static generation"
- "CRITICAL: Migration system detected - must handle ORM dependencies separately"
- "Build script includes heavy operations - optimize to prevent OOM"Artifact Output
After completing all 17 analysis steps and displaying the analysis summary in conversation, write the structured artifact to disk:
File: docker-build/analysis.json
Instructions: 1. Create the docker-build/ directory if it does not exist: Bash: mkdir -p docker-build 2. Write the JSON file using the Write tool with path docker-build/analysis.json 3. Populate all fields from the analysis results gathered in Steps 1–17 4. Set generated_at to current ISO-8601 timestamp 5. Set detection_steps_completed to 17 (or the actual count if any steps were skipped)
Schema:
{
"schema_version": "1.0",
"generated_at": "<ISO-8601 timestamp>",
"phase": "analysis",
"project": {
"language": "typescript",
"framework": "nextjs",
"framework_version": "14.x",
"package_manager": "pnpm",
"package_manager_version": "10.20.0"
},
"package_manager_config": {
"lockfile_disabled": false,
"config_file": ".npmrc",
"install_command": "pnpm install --frozen-lockfile"
},
"workspace": {
"enabled": true,
"type": "pnpm",
"config_file": "pnpm-workspace.yaml",
"packages": ["packages/**", "apps/**"],
"patches_dir": "patches",
"package_count": 39
},
"build": {
"command": "npm run build",
"resolved_command": "npx tsx scripts/prebuild.mts && npx next build --webpack",
"output_dir": ".next",
"standalone_mode": true,
"env_required": [
{ "name": "KEY_VAULTS_SECRET", "placeholder": "build-placeholder-32chars" }
]
},
"run": {
"command": "node server.js",
"entry_point": "scripts/serverLauncher/startServer.js",
"port": 3210,
"env_required": ["DATABASE_URL", "KEY_VAULTS_SECRET", "AUTH_SECRET"]
},
"external_services": {
"database": {
"type": "postgres",
"image": "pgvector/pgvector:pg16",
"env_var": "DATABASE_URL",
"has_vector": true
},
"redis": { "required": false },
"s3": { "required": false },
"search": { "type": "none" },
"message_queue": { "type": "none" }
},
"system_libs": [
{ "npm_package": "sharp", "apt_packages": ["libvips-dev"] }
],
"migration_system": {
"detected": true,
"orm": "drizzle",
"migration_dir": "packages/database/migrations",
"migration_count": 76,
"execution_timing": "runtime",
"execution_command": "npx drizzle-kit migrate",
"execution_env_var": "MIGRATION_DB=1",
"standalone_with_orm": true,
"requires_separate_deps": true,
"warnings": [
"Next.js standalone mode + Drizzle ORM - must install drizzle-orm separately",
"76 migration files - ensure runtime execution before first request"
]
},
"build_complexity": {
"original_build_script": "npm run prebuild && next build",
"heavy_operations": [
{ "name": "lint", "essential": false, "recommendation": "Skip in Docker" },
{ "name": "type-check", "essential": false, "recommendation": "Skip in Docker" }
],
"workspace_package_count": 39,
"memory_risk": "high",
"docker_build_command": "npx tsx scripts/prebuild.mts && npx next build --webpack",
"memory_limit": "NODE_OPTIONS=--max-old-space-size=8192",
"skipped_commands": [
{ "command": "npm run lint", "reason": "Config file .eslintrc.js excluded in .dockerignore" }
]
},
"custom_cli": { "detected": false },
"native_modules": { "rust_required": false },
"complexity": "L3",
"max_iterations": 5,
"existing_docker": {
"has_dockerfile": false,
"has_compose": false,
"has_dockerignore": false
},
"warnings": [
"CRITICAL: Migration system detected - must handle ORM dependencies separately"
],
"detection_steps_completed": 17
}The top-level keys are: schema_version, generated_at, phase, project, package_manager_config, workspace, build, run, external_services, system_libs, migration_system, build_complexity, custom_cli, native_modules, complexity, max_iterations, existing_docker, warnings, detection_steps_completed.
Populate each key from the corresponding analysis step results. Use actual detected values — the JSON example above is illustrative (based on a Next.js monorepo) and your output must reflect the actual project.
Note: This file is written to docker-build/ to keep artifacts separate from the generated Docker files (Dockerfile, .dockerignore, etc.) which remain at the project root.
#!/usr/bin/env node
/**
* Dockerfile Validation Script
*
* Checks generated Dockerfile for common issues before build.
* Exit code 0 = valid, exit code 1 = has errors.
*
* Usage: node validate-dockerfile.mjs <dockerfile-path> [--port=3000] [--json]
*/
import fs from 'fs';
import path from 'path';
function validate(dockerfilePath, opts = {}) {
const content = fs.readFileSync(dockerfilePath, 'utf-8');
const lines = content.split('\n');
const issues = [];
// 1. No :latest tags
for (let i = 0; i < lines.length; i++) {
if (/^FROM\s+\S+:latest/i.test(lines[i])) {
issues.push({ severity: 'error', line: i + 1, rule: 'no-latest', msg: `Using :latest tag: ${lines[i].trim()}` });
}
}
// 2. Has non-root USER
if (!/^USER\s+(?!root)/m.test(content)) {
issues.push({ severity: 'warn', rule: 'non-root-user', msg: 'No non-root USER instruction found' });
}
// 3. Multi-stage build (when build step likely needed)
const fromCount = (content.match(/^FROM\s/gm) || []).length;
if (fromCount < 2 && /RUN\s+.*\b(build|compile)\b/i.test(content)) {
issues.push({ severity: 'warn', rule: 'multi-stage', msg: 'Single-stage build detected with build step — consider multi-stage' });
}
// 4. COPY . before dependency install (cache busting)
const copyAllIdx = lines.findIndex(l => /^COPY\s+\.\s+\./.test(l));
const installIdx = lines.findIndex(l => /npm ci|npm install|pnpm install|yarn install|pip install|go mod download/.test(l));
if (copyAllIdx !== -1 && installIdx !== -1 && copyAllIdx < installIdx) {
issues.push({ severity: 'error', rule: 'copy-before-install', msg: 'COPY . . before dependency install breaks Docker cache' });
}
// 5. EXPOSE matches expected port
if (opts.port) {
const exposeMatch = content.match(/^EXPOSE\s+(\d+)/m);
if (exposeMatch && exposeMatch[1] !== String(opts.port)) {
issues.push({ severity: 'warn', rule: 'port-mismatch', msg: `EXPOSE ${exposeMatch[1]} doesn't match expected port ${opts.port}` });
}
if (!exposeMatch) {
issues.push({ severity: 'warn', rule: 'no-expose', msg: 'No EXPOSE instruction found' });
}
}
// 6. -dev packages in runtime stage (after last FROM)
const lastFromIdx = lines.reduce((acc, l, i) => /^FROM\s/.test(l) ? i : acc, 0);
const runtimeSection = lines.slice(lastFromIdx).join('\n');
const devPkgs = runtimeSection.match(/(lib\w+-dev|python3-dev|gcc|g\+\+|make|build-essential)/g);
if (devPkgs && fromCount > 1) {
issues.push({ severity: 'warn', rule: 'dev-in-runtime', msg: `Build-time packages in runtime stage: ${[...new Set(devPkgs)].join(', ')}` });
}
// 7. Has CMD or ENTRYPOINT
if (!/^(CMD|ENTRYPOINT)\s/m.test(content)) {
issues.push({ severity: 'error', rule: 'no-cmd', msg: 'No CMD or ENTRYPOINT instruction found' });
}
// 8. .dockerignore exists
const dir = path.dirname(dockerfilePath);
if (!fs.existsSync(path.join(dir, '.dockerignore'))) {
issues.push({ severity: 'warn', rule: 'no-dockerignore', msg: 'No .dockerignore file found' });
}
const errors = issues.filter(i => i.severity === 'error');
const warnings = issues.filter(i => i.severity === 'warn');
return {
valid: errors.length === 0,
errors: errors.length,
warnings: warnings.length,
issues,
};
}
// ─── CLI ────────────────────────────────────────────────────
const args = process.argv.slice(2);
const dockerfilePath = args.find(a => !a.startsWith('--'));
const portFlag = args.find(a => a.startsWith('--port='));
const jsonFlag = args.includes('--json');
const port = portFlag ? parseInt(portFlag.split('=')[1]) : null;
if (dockerfilePath) {
const absPath = path.resolve(dockerfilePath);
if (!fs.existsSync(absPath)) {
console.error(`File not found: ${absPath}`);
process.exit(1);
}
const result = validate(absPath, { port });
if (jsonFlag) {
console.log(JSON.stringify(result, null, 2));
} else {
if (result.errors > 0) console.log(`✗ ${result.errors} error(s), ${result.warnings} warning(s)`);
else if (result.warnings > 0) console.log(`⚠ ${result.warnings} warning(s), no errors`);
else console.log('✓ Dockerfile validation passed');
for (const i of result.issues) {
const icon = i.severity === 'error' ? '✗' : '⚠';
const loc = i.line ? `:${i.line}` : '';
console.log(` ${icon} [${i.rule}]${loc} ${i.msg}`);
}
}
process.exit(result.valid ? 0 : 1);
}
export { validate };
#!/bin/sh
set -e
echo "==> Running Drizzle migrations..."
export NODE_PATH=/app/node_modules:/deps/node_modules
if [ -d "{{MIGRATION_DIR}}" ]; then
node -e "
const { drizzle } = require('drizzle-orm/node-postgres');
const { migrate } = require('drizzle-orm/node-postgres/migrator');
const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzle(pool);
migrate(db, { migrationsFolder: '{{MIGRATION_DIR}}' })
.then(() => { console.log('Migrations complete'); pool.end(); })
.catch(e => { console.error('Migration failed:', e); process.exit(1); });
"
fi
echo "==> Starting application..."
exec node {{ENTRY_FILE}}
#!/bin/sh
set -e
echo "==> Starting application..."
exec node {{ENTRY_FILE}}
#!/bin/sh
set -e
echo "==> Running Prisma migrations..."
npx prisma migrate deploy
echo "==> Starting application..."
exec node {{ENTRY_FILE}}
#!/bin/sh
set -e
echo "==> Running TypeORM migrations..."
npx typeorm migration:run -d {{DATASOURCE_PATH}}
echo "==> Starting application..."
exec node {{ENTRY_FILE}}
# syntax=docker/dockerfile:1.4
# ============================================
# Stage 1: Build
# ============================================
FROM golang:1.21.6-alpine AS build
WORKDIR /app
# Install git for private modules (if needed)
RUN apk add --no-cache git ca-certificates
# Copy go mod files
COPY go.mod go.sum ./
# Download dependencies with cache mount
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
# Copy source code
COPY . .
# Build static binary
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-w -s" -o main .
# ============================================
# Stage 2: Runtime
# ============================================
FROM alpine:3.19 AS runtime
WORKDIR /app
# Install CA certificates for HTTPS
RUN apk --no-cache add ca-certificates tzdata
# Set environment variables
ENV PORT=8080
ENV GIN_MODE=release
# Copy binary from build stage
COPY --from=build /app/main .
# Copy static/config files if needed
# COPY --from=build /app/config ./config
# COPY --from=build /app/templates ./templates
# Create non-root user
RUN adduser -D -u 1000 appuser
USER appuser
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:8080/health || exit 1
# Start application
CMD ["./main"]
# ============================================
# Alternative: Scratch image (smallest possible)
# Use only for fully static binaries
# ============================================
# FROM scratch AS runtime-scratch
#
# WORKDIR /app
#
# COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# COPY --from=build /app/main .
#
# USER 1000:1000
#
# EXPOSE 8080
#
# CMD ["./main"]