Containerization for TypeScript/Node.js applications. Use when deploying Node.js backends, need consistent dev environments, or setting up CI/CD pipelines. Covers multi-stage builds, docker-compose for development, and production optimization. Choose this skill for containerizing tRPC/Express APIs with Prisma.
Scanned 2/12/2026
Install via CLI
openskills install majiayu000/claude-skill-registry---
name: docker-node
description: Containerization for TypeScript/Node.js applications. Use when deploying Node.js backends, need consistent dev environments, or setting up CI/CD pipelines. Covers multi-stage builds, docker-compose for development, and production optimization. Choose this skill for containerizing tRPC/Express APIs with Prisma.
allowed-tools: Read, Edit, Write, Bash (*)
---
# Docker (Node.js Containerization)
## Overview
Docker enables consistent environments for Node.js applications across development, testing, and production. Multi-stage builds reduce image size, docker-compose simplifies local development.
**Base Image**: `node:20-alpine` (recommended for small size)
**Use case**: Deploy TypeScript APIs, ensure consistent environments
**Key Benefit**: "Works on my machine" → "Works everywhere"
## When to Use This Skill
✅ **Use Docker when:**
- Deploying to cloud (AWS, GCP, Azure)
- Need consistent dev environment across team
- Running CI/CD pipelines
- Deploying with Kubernetes
- Need isolated PostgreSQL/Redis for development
❌ **Skip Docker when:**
- Simple scripts or CLI tools
- Serverless deployments (use platform's build)
- Early prototyping (adds complexity)
---
## Multi-Stage Dockerfile (Production)
```dockerfile
# Dockerfile
# Stage 1: Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
# Stage 2: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json ./
COPY prisma ./prisma/
COPY src ./src/
RUN npx prisma generate
RUN npm run build
# Stage 3: Production
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
# Security: non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001 -G nodejs
# Copy production artifacts
COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/prisma ./prisma
COPY --from=builder --chown=nodejs:nodejs /app/node_modules/.prisma ./node_modules/.prisma
USER nodejs
EXPOSE 3000
# Run migrations then start
CMD ["sh", "-c", "npx prisma migrate deploy && node dist/index.js"]
```
---
## Docker Compose (Development)
```yaml
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
target: builder # Use builder stage for dev
ports:
- "3000:3000"
environment:
NODE_ENV: development
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/myapp
volumes:
- ./src:/app/src:delegated # Hot reload
- ./prisma:/app/prisma:delegated
depends_on:
postgres:
condition: service_healthy
command: npm run dev
postgres:
image: postgres:15-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: myapp
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d myapp"]
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
```
---
## .dockerignore
```dockerignore
# Dependencies
node_modules
# Build outputs
dist
.next
# Git
.git
.gitignore
# Environment
.env
.env.*
!.env.example
# IDE
.vscode
.idea
# Docker
docker-compose*
Dockerfile*
# Tests & docs
coverage
*.md
**/*.test.ts
**/*.spec.ts
# OS
.DS_Store
Thumbs.db
```
---
## Commands
### Development
```bash
# Start all services
docker-compose up
# Start with rebuild
docker-compose up --build
# Start in background
docker-compose up -d
# View logs
docker-compose logs -f app
# Stop all
docker-compose down
# Stop and remove volumes (reset DB)
docker-compose down -v
```
### Production Build
```bash
# Build production image
docker build -t myapp:latest .
# Run container
docker run -p 3000:3000 \
-e DATABASE_URL="postgresql://..." \
-e JWT_SECRET="..." \
myapp:latest
# Run with env file
docker run -p 3000:3000 --env-file .env.production myapp:latest
```
### Debug
```bash
# Shell into running container
docker-compose exec app sh
# Shell into new container
docker run -it myapp:latest sh
# View container processes
docker-compose top
# Check image size
docker images myapp
```
---
## Database Migrations in Docker
### Development (Auto-migrate)
```yaml
# docker-compose.yml
app:
command: sh -c "npx prisma migrate dev && npm run dev"
```
### Production (Deploy migrations)
```dockerfile
# In Dockerfile CMD
CMD ["sh", "-c", "npx prisma migrate deploy && node dist/index.js"]
```
### CI/CD Pipeline
```bash
# Run migrations in separate step
docker run --rm \
-e DATABASE_URL="$PROD_DATABASE_URL" \
myapp:latest \
npx prisma migrate deploy
```
---
## Health Checks
### Application Health
```dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
```
### Health Endpoint
```typescript
// src/routes/health.ts
app.get('/health', async (req, res) => {
try {
await prisma.$queryRaw`SELECT 1`;
res.json({ status: 'healthy', db: 'connected' });
} catch {
res.status(503).json({ status: 'unhealthy', db: 'disconnected' });
}
});
```
---
## Environment Variables
### Development
```yaml
# docker-compose.yml
services:
app:
environment:
NODE_ENV: development
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/myapp
JWT_SECRET: dev-secret-not-for-production
```
### Production
```bash
# Pass at runtime
docker run -e DATABASE_URL="..." -e JWT_SECRET="..." myapp
# Or use env file
docker run --env-file .env.production myapp
```
---
## Optimization Tips
### Reduce Image Size
```dockerfile
# Use alpine base
FROM node:20-alpine
# Clean npm cache
RUN npm ci && npm cache clean --force
# Don't install devDependencies in production
RUN npm ci --only=production
```
### Layer Caching
```dockerfile
# Copy package files first (changes less often)
COPY package*.json ./
RUN npm ci
# Then copy source (changes more often)
COPY . .
RUN npm run build
```
### Security
```dockerfile
# Run as non-root
RUN adduser -S nodejs
USER nodejs
# Don't expose unnecessary ports
EXPOSE 3000
# Use specific versions
FROM node:20.10-alpine
```
---
## Rules
### Do ✅
- Use multi-stage builds for production
- Run containers as non-root user
- Use `.dockerignore` to exclude unnecessary files
- Add health checks
- Pin base image versions
- Use `docker-compose` for local development
### Avoid ❌
- Running as root in production
- Storing secrets in Dockerfile
- Using `latest` tag in production
- Including `node_modules` in image
- Skipping `.dockerignore`
---
## Troubleshooting
```yaml
"Prisma client not generated":
→ Add RUN npx prisma generate in builder stage
→ Copy node_modules/.prisma to runner stage
"Permission denied":
→ Check file ownership with --chown
→ Ensure USER matches file owner
"Container exits immediately":
→ Check logs: docker-compose logs app
→ Verify CMD is blocking (not backgrounded)
"Can't connect to database":
→ Use service name as host (postgres, not localhost)
→ Check depends_on with healthcheck
→ Verify DATABASE_URL uses container network
"Image too large":
→ Use alpine base image
→ Add .dockerignore
→ Use multi-stage builds
→ Clean npm cache
```
---
## File Structure
```
project/
├── Dockerfile
├── docker-compose.yml
├── .dockerignore
├── .env.example
├── package.json
├── tsconfig.json
├── prisma/
│ └── schema.prisma
└── src/
└── index.ts
```
## References
- https://docs.docker.com — Official documentation
- https://docs.docker.com/compose/ — Docker Compose
- https://hub.docker.com/_/node — Node.js images
No comments yet. Be the first to comment!