Skip to content
Back to skills

Docker Prod

CSecurity

Docker prod — image optimization, multi-stage builds, best practices.

  • 6 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 5, 2026
developmentphpsqlnodedockergitsecurity

Security analysis

C73/100
  • highPerforms destructive filesystem operations
  • criticalExfiltrates credentials via HTTP — exact pattern from Snyk ToxicSkills study

Pro shows the line behind each finding and how to fix it

Scanned September 5, 2026

npx -y skills add Dev-Toolbelt/dev-team-agents --skill docker-prod --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Docker Prod?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Docker Prod
[![Security: C — Skills Directory](https://www.skillsdirectory.com/api/skills/dev-toolbelt-docker-prod/badge)](https://www.skillsdirectory.com/skills/dev-toolbelt-docker-prod)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: docker-prod
description: Docker prod — image optimization, multi-stage builds, best practices.
---

# Docker for Production

## Core Principles

- **Minimal images**: only what the app needs to run — no dev tools, no build artifacts
- **Multi-stage builds**: separate build and runtime stages
- **Non-root user**: never run processes as root
- **Read-only filesystem** where possible
- **Explicit versions**: never use `latest` tags in production
- **Health checks**: always define them so orchestrators know when a container is ready

---

## Multi-Stage Dockerfile

```dockerfile
# ─── Stage 1: Build ───────────────────────────────────────────
FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

# ─── Stage 2: Runtime ─────────────────────────────────────────
FROM node:20-alpine AS runtime

# Security: non-root user
RUN addgroup -g 1001 appgroup && \
    adduser -D -u 1001 -G appgroup appuser

WORKDIR /app

# Copy only what's needed
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json .

USER appuser

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
  CMD wget -qO- http://localhost:3000/health || exit 1

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

## PHP (Laravel/FrankenPHP) Example

```dockerfile
FROM dunglas/frankenphp:1-php8.3-alpine AS base

RUN install-php-extensions \
    pdo_pgsql \
    redis \
    opcache \
    intl

# ─── Build stage ──────────────────────────────────────────────
FROM base AS builder

COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --prefer-dist --optimize-autoloader

COPY . .
RUN composer run-script post-autoload-dump

# ─── Runtime stage ────────────────────────────────────────────
FROM base AS runtime

RUN addgroup -g 1001 appgroup && \
    adduser -D -u 1001 -G appgroup www-data-custom

WORKDIR /app

COPY --from=builder --chown=www-data-custom:appgroup /app .

USER www-data-custom

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
  CMD wget -qO- http://localhost:8080/up || exit 1

CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"]
```

## Production docker-compose.yml

```yaml
services:
  app:
    image: registry.example.com/myapp:${IMAGE_TAG:-latest}
    container_name: myapp
    restart: always
    environment:
      - APP_ENV=production
    env_file:
      - .env.production
    ports:
      - "127.0.0.1:8000:8000"   # bind to loopback — nginx/caddy handles external
    read_only: true              # filesystem is read-only
    tmpfs:
      - /tmp                     # writable temp
      - /run
    depends_on:
      db:
        condition: service_healthy
    networks:
      - internal
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: 512M

  db:
    image: postgres:16-alpine
    restart: always
    volumes:
      - db_data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    networks:
      - internal

secrets:
  db_password:
    file: ./secrets/db_password.txt

volumes:
  db_data:

networks:
  internal:
    driver: bridge
    internal: true   # no external access
```

## Image Size Optimization

- Use Alpine or Distroless base images
- Combine `RUN` commands to reduce layers: `RUN apt-get update && apt-get install -y pkg && rm -rf /var/lib/apt/lists/*`
- Use `.dockerignore` to exclude: `.git`, `node_modules`, `*.log`, `tests/`, `docs/`
- Multi-stage builds to exclude build tools from final image

## Security Checklist

- [ ] Non-root user
- [ ] No secrets in `ENV` or image layers — use Docker secrets or env file (not committed)
- [ ] Image pinned to specific digest or version tag
- [ ] Healthcheck defined
- [ ] Ports bound to `127.0.0.1` if behind reverse proxy
- [ ] Resource limits set
- [ ] `.dockerignore` excludes sensitive files
- [ ] Image scanned (Trivy, Snyk, Docker Scout) before push

## Logging

Configure structured JSON logging and send to stdout/stderr — let the orchestrator handle log routing:
```
LOG_FORMAT=json
LOG_LEVEL=info
```
Never write logs to files inside the container.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.