Claude Code Plugins

Community-maintained marketplace

Feedback

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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 .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

"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