| 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
# 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)
# 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
# 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
# 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
# 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
# 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)
# docker-compose.yml
app:
command: sh -c "npx prisma migrate dev && npm run dev"
Production (Deploy migrations)
# In Dockerfile CMD
CMD ["sh", "-c", "npx prisma migrate deploy && node dist/index.js"]
CI/CD Pipeline
# Run migrations in separate step
docker run --rm \
-e DATABASE_URL="$PROD_DATABASE_URL" \
myapp:latest \
npx prisma migrate deploy
Health Checks
Application Health
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
// 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
# 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
# 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
# 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
# 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
# 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
.dockerignoreto exclude unnecessary files - Add health checks
- Pin base image versions
- Use
docker-composefor local development
Avoid ❌
- Running as root in production
- Storing secrets in Dockerfile
- Using
latesttag in production - Including
node_modulesin image - Skipping
.dockerignore
Troubleshooting
"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