
Docker Multi Stage
- 30 installs
- 2 repo stars
- Updated January 5, 2026
- pluginagentmarketplace/custom-plugin-docker
Generate a production Node.js Docker image using a three-stage Alpine build with non-root user and health checks.
About
Docker Multi-Stage is an agent skill that ships a concrete Node.js Dockerfile pattern for solo builders who want smaller images and safer defaults without hand-rolling every layer. The template separates dependency install, compile/build, and a minimal production stage so devDependencies never bloat what runs in the cluster or on a VPS. It uses Alpine, npm ci, npm prune --production, and copies dist plus trimmed node_modules into a non-root nextjs user context with an HTTP health check—typical for shipping a compiled Node or Next-style backend after the Build phase. Invoke it when you are preparing first production deploy, tightening an existing bloated image, or teaching your coding agent consistent container conventions. It is a template skill, not a full CI pipeline; you still wire registry push, secrets, and orchestration separately.
- Three stages: deps (npm ci), builder (npm run build + prune), production Alpine runtime
- node:20-alpine base with non-root nextjs user (uid 1001)
- Copies only production node_modules and dist output into final image
- HEALTHCHECK against HTTP on port 3000 with wget
- NODE_ENV=production and PORT=3000 baked into runtime stage
Docker Multi Stage by the numbers
- 30 all-time installs (skills.sh)
- Ranked #859 of 1,453 DevOps & CI/CD skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Jul 26, 2026 (Skillselion catalog sync)
npx skills add https://github.com/pluginagentmarketplace/custom-plugin-docker --skill docker-multi-stageAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 30 |
|---|---|
| repo stars | ★ 2 |
| Security audit | 3 / 3 scanners passed |
| Last updated | January 5, 2026 |
| Repository | pluginagentmarketplace/custom-plugin-docker ↗ |
What it does
Generate a production Node.js Docker image using a three-stage Alpine build with non-root user and health checks.
Files
Docker Multi-Stage Builds Skill
Create optimized, minimal production images using multi-stage builds with language-specific patterns.
Purpose
Reduce image size by 50-90% by separating build dependencies from runtime, following 2024-2025 best practices.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| language | enum | Yes | - | node/python/go/rust/java |
| target | string | No | runtime | Build target stage |
| base_runtime | string | No | - | Custom runtime base image |
Multi-Stage Patterns
Node.js (Alpine + Distroless)
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --production
# Runtime stage (distroless = minimal attack surface)
FROM gcr.io/distroless/nodejs20-debian12 AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER nonroot
CMD ["dist/index.js"]Python (Slim + Virtual Environment)
# Build stage
FROM python:3.12-slim AS builder
WORKDIR /app
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Runtime stage
FROM python:3.12-slim AS runtime
WORKDIR /app
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY . .
USER nobody
CMD ["python", "main.py"]Go (Scratch = Smallest Possible)
# Build stage
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server
# Runtime stage (scratch = 0 base size)
FROM scratch AS runtime
COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
USER 65534
ENTRYPOINT ["/server"]Rust (Musl for Static Linking)
# Build stage
FROM rust:1.75-alpine AS builder
RUN apk add --no-cache musl-dev
WORKDIR /app
COPY . .
RUN cargo build --release --target x86_64-unknown-linux-musl
# Runtime stage
FROM scratch AS runtime
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/app /app
USER 65534
ENTRYPOINT ["/app"]Java (JRE Only Runtime)
# Build stage
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY . .
RUN ./gradlew build --no-daemon
# Runtime stage (JRE only, not JDK)
FROM eclipse-temurin:21-jre-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
USER nobody
ENTRYPOINT ["java", "-jar", "app.jar"]Size Comparison
| Language | Before | After | Reduction |
|---|---|---|---|
| Node.js | 1.2GB | 150MB | 87% |
| Python | 900MB | 120MB | 87% |
| Go | 800MB | 10MB | 99% |
| Rust | 1.5GB | 5MB | 99.7% |
| Java | 600MB | 200MB | 67% |
Error Handling
Common Errors
| Error | Cause | Solution |
|---|---|---|
COPY --from failed | Stage not found | Check stage name |
not found at runtime | Missing libs | Use alpine, not scratch |
permission denied | Non-root user | COPY --chown |
Fallback Strategy
1. Start with alpine instead of scratch/distroless 2. Add required libraries incrementally 3. Use ldd to identify missing dependencies
Troubleshooting
Debug Checklist
- [ ] All required files copied to runtime stage?
- [ ] SSL certificates included for HTTPS?
- [ ] User/group exists in runtime image?
- [ ] Build artifacts correctly located?
Debug Commands
# Check final image size
docker images myapp:latest
# Inspect layers
docker history myapp:latest --no-trunc
# Compare with baseline
dive myapp:latestUsage
Skill("docker-multi-stage")Assets
assets/Dockerfile.node-multistage- Node.js templateassets/Dockerfile.python-multistage- Python template
Related Skills
- docker-optimization
- dockerfile-basics
# Node.js Multi-Stage Dockerfile
# Optimized for production with minimal image size
# ============================================
# Stage 1: Dependencies
# ============================================
FROM node:20-alpine AS deps
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install ALL dependencies (including dev)
RUN npm ci
# ============================================
# Stage 2: Builder
# ============================================
FROM node:20-alpine AS builder
WORKDIR /app
# Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
# Copy source code
COPY . .
# Build the application
RUN npm run build
# Prune dev dependencies
RUN npm prune --production
# ============================================
# Stage 3: Production
# ============================================
FROM node:20-alpine AS production
# Security: Run as non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
WORKDIR /app
# Copy only production dependencies
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
# Copy built application
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./
# Set environment
ENV NODE_ENV=production
ENV PORT=3000
# Switch to non-root user
USER nextjs
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
# Start application
CMD ["node", "dist/index.js"]
# ============================================
# Build: docker build -t myapp:prod .
# Run: docker run -p 3000:3000 myapp:prod
# ============================================
# Python Multi-Stage Dockerfile
# Optimized for production with minimal image size
# ============================================
# Stage 1: Builder
# ============================================
FROM python:3.12-slim AS builder
WORKDIR /app
# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# ============================================
# Stage 2: Production
# ============================================
FROM python:3.12-slim AS production
# Security: Create non-root user
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
WORKDIR /app
# Copy virtual environment from builder
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Copy application code
COPY --chown=appuser:appgroup . .
# Remove unnecessary files
RUN rm -rf tests/ docs/ *.md Makefile
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONFAULTHANDLER=1
# Switch to non-root user
USER appuser
# Expose port
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
# Run application
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# ============================================
# Build: docker build -t myapp:prod .
# Run: docker run -p 8000:8000 myapp:prod
# ============================================
Multi-Stage Build Patterns
Pattern 1: Build and Production
The most common pattern - build in one stage, run in another.
# Build stage
FROM node:20 AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build
# Production stage
FROM node:20-alpine
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/index.js"]Pattern 2: Dependency Caching
Separate dependency installation for better caching.
# Dependencies stage
FROM node:20-alpine AS deps
COPY package*.json ./
RUN npm ci
# Build stage
FROM node:20-alpine AS builder
COPY --from=deps /node_modules ./node_modules
COPY . .
RUN npm run build
# Production
FROM node:20-alpine
COPY --from=builder /dist ./distPattern 3: Testing Stage
Include testing in the build pipeline.
FROM python:3.12 AS base
COPY requirements.txt .
RUN pip install -r requirements.txt
FROM base AS test
COPY . .
RUN pytest
FROM base AS production
COPY --from=test /app .Pattern 4: Development and Production
Same Dockerfile for dev and prod.
FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./
FROM base AS development
RUN npm install
CMD ["npm", "run", "dev"]
FROM base AS production
RUN npm ci --only=production
COPY . .
CMD ["npm", "start"]Build with: docker build --target=development -t app:dev .
Size Comparison Example
| Stage | Image | Size |
|---|---|---|
| Single stage | node:20 | ~1GB |
| Multi-stage | node:20-alpine | ~150MB |
| Distroless | gcr.io/distroless | ~50MB |
Best Practices
1. Name your stages - Use AS stagename 2. Order matters - Put frequently changing steps last 3. Copy only what's needed - Use specific paths 4. Clean up in same layer - RUN apt-get install && rm -rf /var/lib/apt/lists/* 5. Use .dockerignore - Exclude node_modules, .git, etc.
#!/bin/bash
# Docker Image Size Analyzer
# Compares image sizes and shows layer breakdown
# Usage: ./image-size-analyzer.sh <image1> [image2]
set -e
IMAGE1=${1:-}
IMAGE2=${2:-}
if [ -z "$IMAGE1" ]; then
echo "Usage: $0 <image1> [image2]"
echo "Example: $0 myapp:dev myapp:prod"
exit 1
fi
echo "=========================================="
echo "Docker Image Size Analysis"
echo "=========================================="
analyze_image() {
local IMAGE=$1
echo ""
echo "Image: $IMAGE"
echo "----------------------------------------"
# Get total size
SIZE=$(docker images "$IMAGE" --format "{{.Size}}")
echo "Total Size: $SIZE"
# Show layer breakdown
echo ""
echo "Layer Breakdown:"
docker history "$IMAGE" --format "{{.Size}}\t{{.CreatedBy}}" | \
head -20 | \
awk -F'\t' '{printf "%-10s %s\n", $1, substr($2, 1, 70)}'
# Count layers
LAYERS=$(docker history "$IMAGE" -q | wc -l)
echo ""
echo "Total Layers: $LAYERS"
}
analyze_image "$IMAGE1"
if [ -n "$IMAGE2" ]; then
analyze_image "$IMAGE2"
echo ""
echo "=========================================="
echo "Comparison"
echo "=========================================="
SIZE1=$(docker images "$IMAGE1" --format "{{.Size}}")
SIZE2=$(docker images "$IMAGE2" --format "{{.Size}}")
echo "$IMAGE1: $SIZE1"
echo "$IMAGE2: $SIZE2"
# Convert to bytes for comparison
BYTES1=$(docker images "$IMAGE1" --format "{{.VirtualSize}}")
BYTES2=$(docker images "$IMAGE2" --format "{{.VirtualSize}}")
if [ "$BYTES2" -lt "$BYTES1" ]; then
SAVED=$((BYTES1 - BYTES2))
PERCENT=$((SAVED * 100 / BYTES1))
echo ""
echo "Savings: $PERCENT% reduction"
fi
fi
echo ""
echo "=========================================="
echo "Optimization Tips"
echo "=========================================="
echo "1. Use multi-stage builds"
echo "2. Use alpine/slim base images"
echo "3. Combine RUN commands to reduce layers"
echo "4. Use .dockerignore to exclude files"
echo "5. Remove package manager caches"
echo "6. Don't install unnecessary packages"
Related skills
FAQ
Is Docker Multi Stage safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.