infra-ci-cd-docker
Docker containerization patterns for Node.js/TypeScript development and production
Works with
---
name: infra-ci-cd-docker
description: Docker containerization patterns for Node.js/TypeScript development and production
license: MIT
---
# Docker Containerization Patterns
> **Quick Guide:** Docker with BuildKit for containerizing Node.js/TypeScript applications. Multi-stage builds for minimal production images (1GB to under 100MB). Docker Compose v2 for development environments. BuildKit cache mounts for 10x faster dependency installs. Non-root users, health checks, and secret mounts for production security. Alpine for size, Debian slim for compatibility.
---
<critical_requirements>
## CRITICAL: Before Using This Skill
> **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
**(You MUST use multi-stage builds for production images - NEVER ship dev dependencies, TypeScript compiler, or source files in production)**
**(You MUST run containers as non-root user - NEVER run production containers as root (default))**
**(You MUST use `CMD ["node", "server.js"]` (exec form) - NEVER use `npm start` or shell form as CMD)**
**(You MUST use BuildKit secret mounts for sensitive data at build time - NEVER use ARG or ENV for secrets)**
**(You MUST copy package.json/lockfile BEFORE source code - NEVER `COPY . .` before `npm ci` (breaks layer cache))**
</critical_requirements>
---
## Examples
- [Dockerfile Patterns](examples/core.md) - Multi-stage builds, Bun, monorepo, layer caching, .dockerignore, signal handling
- [Docker Compose](examples/compose.md) - Development environments, networking, volumes, healthchecks
- [Production & CI/CD](examples/production.md) - Security hardening, secrets, CI/CD pipelines, vulnerability scanning
- [Quick Reference](reference.md) - Dockerfile instructions, CLI commands, base image comparison
---
**Auto-detection:** Dockerfile, docker-compose, compose.yaml, Docker, container, multi-stage build, BuildKit, .dockerignore, Docker Compose, docker build, docker run, HEALTHCHECK, Docker Scout, docker init, containerize, container image, Docker network, Docker volume
**When to use:**
- Creating Dockerfiles for Node.js/TypeScript applications
- Setting up multi-stage builds to minimize production image size
- Configuring Docker Compose for local development environments
- Optimizing Docker layer caching and BuildKit cache mounts
- Implementing container security (non-root, secrets, read-only filesystem)
- Setting up health checks for container orchestration
- Building CI/CD pipelines that build and push Docker images
- Teams needing consistent development environments across OS platforms
- Microservice architectures requiring isolated services with dependencies
**When NOT to use:**
- Serverless deployments (AWS Lambda, Vercel Functions) that don't use containers
- Static site hosting (Netlify, Vercel, Cloudflare Pages) with no server runtime
- Simple scripts or CLI tools distributed via npm
- Local development without containerization requirements
- When added complexity outweighs the isolation benefit
- Kubernetes-specific orchestration patterns (use a Kubernetes skill)
**Key patterns covered:**
- Multi-stage Dockerfile for Node.js/TypeScript (builder pattern)
- Docker Compose v2 development environments
- BuildKit cache mounts and layer optimization
- Container security (non-root, secrets, capabilities, read-only)
- Health checks for production containers
- `.dockerignore` for build context optimization
- Volume mounts (named volumes, bind mounts, tmpfs)
- Docker networking (bridge, host, overlay)
- CI/CD integration (GitHub Actions build-push)
- Signal handling (tini for graceful shutdown)
---
<philosophy>
## Philosophy
Containers provide reproducible, isolated environments that eliminate "works on my machine" problems. Docker is the standard for packaging Node.js/TypeScript applications into portable, lightweight images.
**Core principles:**
1. **Minimal production images** - Ship only what the app needs to run (compiled JS, production deps, runtime)
2. **Layer cache optimization** - Structure Dockerfiles so unchanged layers are reused, making rebuilds fast
3. **Security by default** - Non-root users, no secrets in images, minimal attack surface
4. **Development parity** - Docker Compose mirrors production topology locally
</philosophy>
---
<patterns>
## Core Patterns
### Pattern 1: Multi-Stage Dockerfile for Node.js/TypeScript
Multi-stage builds compile TypeScript in a builder stage, then copy only compiled JS and production dependencies into a minimal runtime image. This reduces images from 1GB+ to under 100MB.
A production Dockerfile uses three stages:
1. **deps** - Install production dependencies only
2. **builder** - Install all dependencies, compile TypeScript
3. **runner** - Copy compiled output and production deps into minimal image
```dockerfile
# Stage 1: Production dependencies
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --omit=dev --no-audit --no-fund
# Stage 2: Build TypeScript
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --no-audit --no-fund
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build
# Stage 3: Production runtime
FROM node:22-alpine AS runner
WORKDIR /app
RUN apk add --no-cache tini
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 --ingroup appgroup appuser
COPY --from=deps --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --chown=appuser:appgroup package.json ./
USER appuser
EXPOSE 3000
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "dist/server.js"]
```
**Why good:** Three-stage build separates concerns, BuildKit cache mount speeds up npm ci, non-root user, tini for signal handling, only production artifacts in final image
See [examples/core.md](examples/core.md) for complete Dockerfiles including Bun and monorepo variants.
---
### Pattern 2: Docker Compose for Development
Docker Compose v2 defines multi-container development environments. Use `compose.yaml` (not `docker-compose.yml`) with the `docker compose` command (no hyphen).
```yaml
# compose.yaml
services:
app:
build:
context: .
target: builder
volumes:
- .:/app:cached
- /app/node_modules
depends_on:
db:
condition: service_healthy
db:
image: postgres:17-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dev"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
```
**Why good:** Health checks on dependencies with `condition: service_healthy`, bind mount with `:cached` for macOS perf, anonymous volume protects node_modules, named volume for database persistence
See [examples/compose.md](examples/compose.md) for full development environments with Redis, networking, and volume patterns.
---
### Pattern 3: BuildKit Cache Mounts
BuildKit (default since Docker Engine 23+) provides cache mounts that persist package manager caches across builds, reducing install times by 10x or more.
```dockerfile
# npm
RUN --mount=type=cache,target=/root/.npm \
npm ci --no-audit --no-fund
# pnpm
RUN --mount=type=cache,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile
```
**Layer ordering rules:** Pin base image versions > Copy dependency manifests > Install dependencies > Copy source code > Build/compile. This maximizes cache hits.
See [examples/core.md](examples/core.md) for cache mount patterns for npm, Bun, pnpm, and apt.
---
### Pattern 4: Container Security
#### Non-Root User
```dockerfile
RUN addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 --ingroup appgroup appuser
COPY --chown=appuser:appgroup --from=builder /app/dist ./dist
USER appuser
```
#### Secret Mounts (Build Time)
```dockerfile
# Secret is ephemeral - never persisted in image layers
RUN --mount=type=secret,id=npm_token,env=NPM_TOKEN \
npm ci --no-audit --no-fund
# Build: docker build --secret id=npm_token,env=NPM_TOKEN .
```
#### Runtime Hardening
```yaml
services:
app:
read_only: true
tmpfs: [/tmp]
security_opt: [no-new-privileges:true]
cap_drop: [ALL]
```
See [examples/production.md](examples/production.md) for complete production compose, secrets management, and CI/CD pipelines.
---
### Pattern 5: Health Checks
Health checks enable orchestrators to detect unresponsive containers and restart them automatically.
```dockerfile
HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=30s \
CMD ["node", "-e", "fetch('http://localhost:3000/health').then(r => { if (!r.ok) process.exit(1) }).catch(() => process.exit(1))"]
```
**Why good:** Uses Node.js built-in fetch (no curl needed in Alpine), start-period allows app startup, checks dedicated health endpoint not just root path
See [examples/production.md](examples/production.md) for health check endpoint implementation and Compose healthcheck patterns.
---
### Pattern 6: .dockerignore
```text
node_modules
dist
.git
.env
.env.*
!.env.example
Dockerfile*
compose.yaml
__tests__
*.test.ts
coverage
.github
```
**Why good:** Excludes node_modules (reinstalled deterministically), .env files (prevents secret leaks), .git (invalidates cache), keeps build context small
See [examples/core.md](examples/core.md) for a comprehensive .dockerignore file.
---
### Pattern 7: Signal Handling and Graceful Shutdown
Node.js as PID 1 does not handle SIGTERM/SIGINT correctly. Use tini as init system.
```dockerfile
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "dist/server.js"]
```
**Why good:** Tini forwards signals to node process, handles zombie process reaping, ensures graceful shutdown on `docker stop`
See [examples/core.md](examples/core.md) for complete TypeScript graceful shutdown implementation.
</patterns>
---
<performance>
## Performance Optimization
### Image Size Reduction
| Technique | Impact |
| ------------------- | ---------------------------------------- |
| Multi-stage builds | 1GB to under 100MB (90%+ reduction) |
| Alpine base image | 135MB vs 1GB (full) vs 200MB (slim) |
| `npm ci --omit=dev` | Removes dev dependencies from production |
| `.dockerignore` | Smaller build context, faster sends |
### Build Speed Optimization
| Technique | Impact |
| -------------------------------------- | ----------------------------------------------- |
| BuildKit cache mounts | 10x faster dependency installs |
| Layer ordering (deps before source) | Cache hit on source-only changes |
| `.dockerignore` excluding node_modules | Prevents sending GBs to daemon |
| `--no-audit --no-fund` flags | Skip unnecessary npm checks |
| Parallel multi-stage builds | BuildKit builds independent stages concurrently |
### CI/CD Build Cache
```yaml
# GitHub Actions - Cache Docker layers
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v7
with:
cache-from: type=gha
cache-to: type=gha,mode=max
```
**Why good:** GitHub Actions cache (`type=gha`) persists layers across CI runs, `mode=max` caches all layers (not just final)
See [examples/production.md](examples/production.md) for complete CI/CD pipeline examples.
</performance>
---
<decision_framework>
## Decision Framework
### Base Image Selection
```
Which base image?
|
+-- Need smallest image? --> Alpine (node:22-alpine, ~135MB)
| +-- musl libc compatibility issues? --> Use Debian slim instead
|
+-- Need maximum compatibility? --> Debian slim (node:22-slim, ~200MB)
| +-- Native extensions work out of the box
|
+-- Need debugging tools? --> Full image (node:22, ~1GB)
| +-- Development only, never for production
|
+-- Maximum security? --> Distroless (gcr.io/distroless/nodejs22-debian12)
+-- No shell, no package manager, smallest attack surface
```
### Compose vs Dockerfile Targets
```
How to manage dev vs prod?
|
+-- Multi-stage Dockerfile with targets
| +-- `docker compose build --target builder` for dev
| +-- Final stage for production
|
+-- Separate compose files
+-- compose.yaml (base)
+-- compose.override.yaml (dev - auto-loaded)
+-- compose.prod.yaml (production)
```
### Volume Strategy
```
What data needs to persist?
|
+-- Source code (dev hot reload) --> Bind mount with :cached
+-- Database files --> Named volume
+-- Temporary/cache data --> tmpfs
+-- Secrets at runtime --> Docker secrets or tmpfs
+-- node_modules in dev --> Anonymous volume (protect from host)
```
</decision_framework>
---
<integration>
## Integration Guide
**Docker ecosystem tools:**
- **Docker Scout**: `docker scout cves` for vulnerability scanning in CLI and CI
- **BuildKit**: Default build engine since Docker Engine 23+, enables cache mounts, secret mounts, multi-platform builds
**CI/CD integration:**
Docker images integrate with any CI/CD pipeline. See [examples/production.md](examples/production.md) for build-push patterns and vulnerability scanning workflows.
**Container orchestration:**
Production images work with any container orchestrator. Health checks (`HEALTHCHECK` instruction) and non-root users are universally required regardless of platform.
**Replaces:**
- VM-based development environments (Docker Compose provides lighter isolation)
- Manual server provisioning (Dockerfiles codify environment setup)
- Node version managers in containers (Dockerfile pins exact Node version via base image tag)
</integration>
---
<red_flags>
## RED FLAGS
**High Priority Issues:**
- Running production containers as root (default behavior - always add USER directive)
- Secrets in ARG/ENV/COPY (persist in image layers - use `--mount=type=secret`)
- Single-stage builds shipping dev dependencies and source code to production
- Using `npm start` as CMD (npm swallows SIGTERM - use `CMD ["node", "dist/server.js"]`)
- No `.dockerignore` file (node_modules sent to daemon, secrets leaked into image)
- Using `:latest` tag for base images (non-deterministic builds)
**Medium Priority Issues:**
- `COPY . .` before `npm ci` (any file change invalidates dependency cache)
- No health check defined (orchestrator cannot detect unresponsive containers)
- `docker-compose.yml` with `version:` key (deprecated - use `compose.yaml` without version)
- `depends_on` without `condition: service_healthy` (container starts before dependency is ready)
- Using `npm install` instead of `npm ci` in Dockerfile (non-deterministic, slower)
**Common Mistakes:**
- Forgetting anonymous volume for node_modules in dev (`/app/node_modules`) causing host to overwrite container's modules
- Not using `:cached` on bind mounts on macOS (significant performance impact)
- Installing build tools (gcc, make, python) in the final stage instead of the builder stage
- Exposing database ports to host in production Compose (only needed for dev)
**Gotchas & Edge Cases:**
- Alpine uses musl libc - some npm packages with native C bindings (bcrypt, sharp, canvas) may fail; use `npm rebuild` or switch to Debian slim
- Node.js `fetch()` is available since Node 18+ (no need for curl in health checks on Alpine)
- BuildKit cache mounts require `# syntax=docker/dockerfile:1` or Docker Engine 23+ with BuildKit enabled by default
- `docker compose down -v` removes named volumes (data loss) - use `docker compose down` without `-v` to preserve data
- Docker Desktop on macOS/Windows has different file system performance than Linux - bind mounts are slower
- `COPY --chown` is more efficient than `COPY` + `RUN chown` (one layer vs two)
- `tini` must be installed explicitly on Alpine (`apk add --no-cache tini`) - it is NOT included by default in `node:alpine` images
- The `node_modules/.cache` directory can grow large in development - add it to `.dockerignore`
</red_flags>
---
<critical_reminders>
## CRITICAL REMINDERS
> **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
**(You MUST use multi-stage builds for production images - NEVER ship dev dependencies, TypeScript compiler, or source files in production)**
**(You MUST run containers as non-root user - NEVER run production containers as root (default))**
**(You MUST use `CMD ["node", "server.js"]` (exec form) - NEVER use `npm start` or shell form as CMD)**
**(You MUST use BuildKit secret mounts for sensitive data at build time - NEVER use ARG or ENV for secrets)**
**(You MUST copy package.json/lockfile BEFORE source code - NEVER `COPY . .` before `npm ci` (breaks layer cache))**
**Failure to follow these rules will result in bloated images (1GB+), security vulnerabilities (root access, leaked secrets), broken graceful shutdown (lost requests on deploy), and slow CI builds (no layer caching).**
</critical_reminders>More DevOps & Infrastructure skills
azure-ai
microsoft/azure-skills
Use for Azure AI: Search, Speech, OpenAI, Document Intelligence. Helps with search, vector/hybrid search, speech-to-text, text-to-speech, transcription, OCR. WHEN: AI Search, query search, vector search, hybrid search, semantic search, speech-to-text, text-to-speech, transcribe, OCR, convert text to speech.
appinsights-instrumentation
microsoft/azure-skills
Guidance for instrumenting webapps with Azure Application Insights. Provides telemetry patterns, SDK setup, and configuration references. WHEN: how to instrument app, App Insights SDK, telemetry patterns, what is App Insights, Application Insights guidance, instrumentation examples, APM best practices.
azure-storage
microsoft/azure-skills
Azure Storage Services including Blob Storage, File Shares, Queue Storage, Table Storage, and Data Lake. Answers questions about storage access tiers (hot, cool, cold, archive), when to use each tier, and tier comparison. Provides object storage, SMB file shares, async messaging, NoSQL key-value, and big data analytics. Includes lifecycle management. USE FOR: blob storage, file shares, queue storage, table storage, data lake, upload files, download blobs, storage accounts, access tiers, storage tiers, hot cool cold archive, storage tier comparison, when to use storage tiers, lifecycle management, Azure Storage concepts. DO NOT USE FOR: SQL databases, Cosmos DB (use azure-prepare), messaging with Event Hubs or Service Bus (use azure-messaging).

