dockerfile-optimizer

Build optimized, secure, and cache-efficient Docker images following production best practices.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "dockerfile-optimizer" with this command: npx skills add monkey1sai/openai-cli/monkey1sai-openai-cli-dockerfile-optimizer

Dockerfile Optimizer

Build optimized, secure, and cache-efficient Docker images following production best practices.

Core Workflow

  • Analyze current Dockerfile: Identify optimization opportunities

  • Implement multi-stage builds: Separate build and runtime

  • Optimize layer caching: Order instructions efficiently

  • Minimize image size: Use slim base images and cleanup

  • Add security hardening: Non-root user, minimal permissions

  • Configure health checks: Ensure container health monitoring

Base Image Selection

Image Size Comparison

Base Image Size Use Case

node:20

~1GB Development only

node:20-slim

~200MB General production

node:20-alpine

~130MB Size-critical production

gcr.io/distroless/nodejs20

~120MB Maximum security

Recommendations by Language

Node.js

FROM node:20-alpine

Python

FROM python:3.12-slim

Go

FROM golang:1.22-alpine AS builder FROM scratch AS runtime # Or gcr.io/distroless/static

Rust

FROM rust:1.75-alpine AS builder FROM alpine:3.19 AS runtime

Java

FROM eclipse-temurin:21-jdk-alpine AS builder FROM eclipse-temurin:21-jre-alpine AS runtime

Multi-Stage Builds

Node.js Application

==================== Build Stage ====================

FROM node:20-alpine AS builder

WORKDIR /app

Install dependencies first (cache layer)

COPY package.json package-lock.json ./ RUN npm ci --ignore-scripts

Copy source and build

COPY . . RUN npm run build

Prune dev dependencies

RUN npm prune --production

==================== Production Stage ====================

FROM node:20-alpine AS production

Security: Create non-root user

RUN addgroup -g 1001 -S nodejs &&
adduser -S nextjs -u 1001

WORKDIR /app

Copy only necessary files

COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist COPY --from=builder --chown=nextjs:nodejs /app/package.json ./

Security: Switch to non-root user

USER nextjs

Health check

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"

EXPOSE 3000

CMD ["node", "dist/index.js"]

Next.js Application

==================== Dependencies ====================

FROM node:20-alpine AS deps

RUN apk add --no-cache libc6-compat

WORKDIR /app

COPY package.json package-lock.json ./ RUN npm ci

==================== Builder ====================

FROM node:20-alpine AS builder

WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules COPY . .

Disable telemetry during build

ENV NEXT_TELEMETRY_DISABLED=1

RUN npm run build

==================== Runner ====================

FROM node:20-alpine AS runner

WORKDIR /app

ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1

RUN addgroup --system --gid 1001 nodejs &&
adduser --system --uid 1001 nextjs

Copy static assets

COPY --from=builder /app/public ./public

Set correct permissions for prerender cache

RUN mkdir .next && chown nextjs:nodejs .next

Copy build output

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT=3000 ENV HOSTNAME="0.0.0.0"

HEALTHCHECK --interval=30s --timeout=3s
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1

CMD ["node", "server.js"]

Python Application

==================== 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 dependencies

COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt

==================== Production ====================

FROM python:3.12-slim AS production

WORKDIR /app

Create non-root user

RUN groupadd -r appuser && useradd -r -g appuser appuser

Copy virtual environment from builder

COPY --from=builder /opt/venv /opt/venv ENV PATH="/opt/venv/bin:$PATH"

Copy application code

COPY --chown=appuser:appuser . .

USER appuser

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=3s
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Go Application

==================== Builder ====================

FROM golang:1.22-alpine AS builder

RUN apk add --no-cache git ca-certificates tzdata

WORKDIR /app

Download dependencies

COPY go.mod go.sum ./ RUN go mod download && go mod verify

Build

COPY . . RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
-ldflags="-w -s -X main.version=$(git describe --tags --always)"
-o /app/server ./cmd/server

==================== Production ====================

FROM scratch AS production

Copy CA certificates for HTTPS

COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo

Copy binary

COPY --from=builder /app/server /server

EXPOSE 8080

ENTRYPOINT ["/server"]

Layer Caching Optimization

Order Instructions by Change Frequency

✓ GOOD: Least changing → Most changing

FROM node:20-alpine

1. System dependencies (rarely change)

RUN apk add --no-cache dumb-init

2. Create user (rarely changes)

RUN adduser -D appuser

3. Set working directory

WORKDIR /app

4. Copy dependency files (change occasionally)

COPY package.json package-lock.json ./

5. Install dependencies (cached if package files unchanged)

RUN npm ci --production

6. Copy source code (changes frequently)

COPY --chown=appuser:appuser . .

USER appuser CMD ["dumb-init", "node", "index.js"]

✗ BAD: Source code before dependencies

FROM node:20-alpine WORKDIR /app COPY . . # Invalidates cache on ANY file change RUN npm install # Must reinstall every time CMD ["node", "index.js"]

.dockerignore

Version control

.git .gitignore

Dependencies (reinstalled in container)

node_modules .pnpm-store

Build outputs

dist build .next out

Development files

.env*.local *.log coverage .nyc_output

IDE

.idea .vscode *.swp *.swo

Docker

Dockerfile* docker-compose* .docker

Documentation

*.md docs

Tests (unless needed in container)

tests *.test.ts .spec.ts jest.config.

Image Size Reduction

Clean Up in Same Layer

✓ GOOD: Install and clean in one layer

RUN apt-get update &&
apt-get install -y --no-install-recommends
curl
ca-certificates
&& rm -rf /var/lib/apt/lists/*
&& apt-get clean

✗ BAD: Separate layers (cleanup doesn't reduce size)

RUN apt-get update RUN apt-get install -y curl RUN rm -rf /var/lib/apt/lists/* # Too late, already in previous layer

Use --no-install-recommends

✓ Minimal installation

RUN apt-get install -y --no-install-recommends package-name

✗ Installs unnecessary recommended packages

RUN apt-get install -y package-name

Alpine Package Management

Alpine uses apk, not apt

RUN apk add --no-cache
curl
git
&& rm -rf /var/cache/apk/*

Security Hardening

Non-Root User

Create user in Debian/Ubuntu

RUN groupadd -r appgroup && useradd -r -g appgroup appuser

Create user in Alpine

RUN addgroup -S appgroup && adduser -S appuser -G appgroup

Set ownership and switch user

COPY --chown=appuser:appgroup . . USER appuser

Read-Only Filesystem

In docker-compose.yml or docker run

services: app: read_only: true tmpfs: - /tmp - /var/run

Security Scanning

Add labels for security scanning

LABEL org.opencontainers.image.source="https://github.com/org/repo" LABEL org.opencontainers.image.description="Application description" LABEL org.opencontainers.image.licenses="MIT"

Minimal Capabilities

docker-compose.yml

services: app: cap_drop: - ALL cap_add: - NET_BIND_SERVICE # Only if binding to port < 1024 security_opt: - no-new-privileges:true

Health Checks

HTTP Health Check

HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3
CMD curl -f http://localhost:3000/health || exit 1

Without curl (smaller image)

Node.js

HEALTHCHECK --interval=30s --timeout=3s --retries=3
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"

Python

HEALTHCHECK --interval=30s --timeout=3s --retries=3
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1

wget (Alpine)

HEALTHCHECK --interval=30s --timeout=3s --retries=3
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

Environment Variables

Build-time arguments

ARG NODE_ENV=production ARG APP_VERSION=unknown

Runtime environment variables

ENV NODE_ENV=$NODE_ENV ENV APP_VERSION=$APP_VERSION

Don't include secrets in Dockerfile

Use docker run --env-file or secrets management

Docker Compose for Development

docker-compose.yml

version: '3.8'

services: app: build: context: . dockerfile: Dockerfile target: development # Multi-stage target volumes: - .:/app - /app/node_modules # Anonymous volume for node_modules ports: - "3000:3000" environment: - NODE_ENV=development command: npm run dev

app-prod: build: context: . dockerfile: Dockerfile target: production ports: - "3000:3000" environment: - NODE_ENV=production healthcheck: test: ["CMD", "wget", "--spider", "http://localhost:3000/health"] interval: 30s timeout: 3s retries: 3

CI/CD Integration

GitHub Actions Build

.github/workflows/docker.yml

name: Docker Build

on: push: branches: [main] pull_request: branches: [main]

jobs: build: runs-on: ubuntu-latest

steps:
  - uses: actions/checkout@v4

  - name: Set up Docker Buildx
    uses: docker/setup-buildx-action@v3

  - name: Login to Container Registry
    uses: docker/login-action@v3
    with:
      registry: ghcr.io
      username: ${{ github.actor }}
      password: ${{ secrets.GITHUB_TOKEN }}

  - name: Build and push
    uses: docker/build-push-action@v5
    with:
      context: .
      push: ${{ github.event_name != 'pull_request' }}
      tags: |
        ghcr.io/${{ github.repository }}:latest
        ghcr.io/${{ github.repository }}:${{ github.sha }}
      cache-from: type=gha
      cache-to: type=gha,mode=max

  - name: Scan for vulnerabilities
    uses: aquasecurity/trivy-action@master
    with:
      image-ref: ghcr.io/${{ github.repository }}:${{ github.sha }}
      format: 'table'
      exit-code: '1'
      severity: 'CRITICAL,HIGH'

Common Optimizations

Pin Versions

✓ Pinned versions for reproducibility

FROM node:20.11.0-alpine3.19

✗ Latest tags can break builds

FROM node:latest FROM node:20-alpine

Use COPY Instead of ADD

✓ COPY is explicit and preferred

COPY package.json .

✗ ADD has extra features rarely needed

ADD package.json . # Only use for URLs or tar extraction

Combine RUN Commands

✓ Single layer, smaller image

RUN apt-get update &&
apt-get install -y package1 package2 &&
rm -rf /var/lib/apt/lists/*

✗ Multiple layers, larger image

RUN apt-get update RUN apt-get install -y package1 RUN apt-get install -y package2

Debugging

Inspect Image Layers

View layer history

docker history image-name

Analyze image with dive

docker run --rm -it
-v /var/run/docker.sock:/var/run/docker.sock
wagoodman/dive:latest image-name

Build with Progress

Detailed build output

docker build --progress=plain -t myapp .

Build specific stage

docker build --target builder -t myapp:builder .

Best Practices

  • Use multi-stage builds: Separate build and runtime environments

  • Order layers by change frequency: Maximize cache hits

  • Use .dockerignore: Exclude unnecessary files

  • Run as non-root: Always create and use a non-root user

  • Pin base image versions: Ensure reproducible builds

  • Clean up in same layer: Reduce image size

  • Add health checks: Enable container orchestration

  • Scan for vulnerabilities: Use Trivy, Snyk, or similar

  • Use slim/alpine bases: Minimize attack surface

  • Don't store secrets: Use runtime injection

Output Checklist

Every optimized Dockerfile should include:

  • Multi-stage build separating build and runtime

  • Slim or Alpine base image

  • Pinned base image version

  • Layer caching optimization (deps before source)

  • Non-root user configuration

  • Health check defined

  • .dockerignore file

  • No secrets in image

  • Minimal installed packages

  • Cleanup in same layer as install

  • Labels for metadata

  • Security scanning in CI

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

responsive-design-system

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

rate-limiting-abuse-protection

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

bruno-collection-generator

No summary provided by upstream source.

Repository SourceNeeds Review
dockerfile-optimizer | V50.AI