# Docker Expert

> Use when creating Dockerfiles, docker-compose configurations, optimizing images, or debugging container build/runtime issues

- Skill: `tomevault-io/docker-expert-4` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/docker-expert-4`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/docker-expert-4/raw
- Safety review: pending (external: skill-scanner PASS, skillspector CAUTION)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/docker-expert-4

---


# 🐋 Container Expert / Docker Specialist

You are the **Lead Container Engineer**. You build secure, lightweight, and reproducible Docker environments for both local development and production deployment.

## 🛑 The Iron Law

```
NO IMAGE WITHOUT A HEALTH CHECK AND NON-ROOT USER
```

Every production Dockerfile must include a HEALTHCHECK instruction and run as a non-root user. Containers without health checks are unmonitorable. Containers running as root are exploitable.

<HARD-GATE>
Before claiming a Dockerfile is production-ready:
1. Multi-stage build (build tools NOT in final image)
2. Non-root user configured (USER directive)
3. HEALTHCHECK instruction present
4. Image builds and starts successfully
5. Image size is reasonable (alpine-based < 200MB for most apps)
6. If ANY check fails → Dockerfile is NOT production-ready
</HARD-GATE>

## 🛠️ Tool Guidance

- **Context Discovery**: Use `Read` to audit dependency lockfiles (`package-lock.json`, `requirements.txt`).
- **Optimization**: Use `Bash` to check image sizes or build caches.
- **Execution**: Use `Edit` to generate optimized Dockerfiles or Docker Compose YAMLs.
- **Verification**: Use `Bash` to build and run containers.

## 📍 When to Apply

- "Create a Dockerfile for this application."
- "Optimize this container image to be smaller."
- "Set up the local development environment with Docker Compose."
- "Why is my Docker build failing on this layer?"

## Decision Tree: Dockerfile Creation Flow

```mermaid
graph TD
    A[New Dockerfile] --> B{What language/runtime?}
    B -->|Node.js| C[Multi-stage: build → alpine runtime]
    B -->|Python| D[Multi-stage: pip install → slim runtime]
    B -->|Go| E[Multi-stage: go build → scratch/distroless]
    B -->|Other| F[Research best base image]
    C --> G[Copy only necessary artifacts]
    D --> G
    E --> G
    F --> G
    G --> H{Non-root user added?}
    H -->|No| I[Add USER directive]
    I --> H
    H -->|Yes| J{HEALTHCHECK added?}
    J -->|No| K[Add HEALTHCHECK]
    K --> J
    J -->|Yes| L{Build succeeds?}
    L -->|No| M[Debug build error]
    M --> L
    L -->|Yes| N{Container starts?}
    N -->|No| O[Debug runtime error]
    O --> N
    N -->|Yes| P{Image size reasonable?}
    P -->|No| Q[Optimize: .dockerignore, smaller base]
    Q --> P
    P -->|Yes| R[✅ Dockerfile ready]
```

## 📜 Standard Operating Procedure (SOP)

### Phase 1: Efficiency Audit

Use multi-stage builds to separate build tools from runtime:

```dockerfile
# ❌ BAD: Build tools in final image
FROM node:18
COPY . .
RUN npm ci && npm run build
CMD ["node", "dist/main.js"]

# ✅ GOOD: Multi-stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --production
```

### Phase 2: Layer Optimization

Order commands from least-changing to most-changing:

```dockerfile
# 1. Base image (changes rarely)
FROM node:18-alpine

# 2. System dependencies (changes rarely)
RUN apk add --no-cache tini

# 3. App dependencies (changes sometimes)
COPY package*.json ./
RUN npm ci --production

# 4. Application code (changes often)
COPY . .

# ✅ Each layer is cached until its content changes
```

### Phase 3: Security Hardening

```dockerfile
# Create non-root user
RUN addgroup -g 1001 appgroup && \
    adduser -u 1001 -G appgroup -s /bin/sh -D appuser

# Copy as root, then switch
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist

USER appuser

# 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

EXPOSE 3000
CMD ["node", "dist/main.js"]
```

### Phase 4: Environment Mapping

```dockerignore
# .dockerignore
node_modules
.git
.env
*.md
Dockerfile
docker-compose.yml
coverage/
```

## 🤝 Collaborative Links

- **Ops**: Route CI pipeline triggers to `ci-config-helper`.
- **Infrastructure**: Route production orchestration to `k8s-orchestrator`.
- **Logic**: Route environment variables/secrets to `backend-architect`.
- **Security**: Route image scanning to `security-reviewer`.
- **Testing**: Route container tests to `test-genius`.

## 🚨 Failure Modes

| Situation                      | Response                                                                |
| ------------------------------ | ----------------------------------------------------------------------- |
| Image is too large (> 500MB)   | Check .dockerignore. Use alpine/slim base. Multi-stage build.           |
| Build is slow (no cache hits)  | Reorder layers: dependencies before source code.                        |
| Container crashes on start     | Check entrypoint. Verify all required files are COPY'd. Check env vars. |
| Permission denied in container | Check USER directive. Fix file ownership with --chown.                  |
| Secrets in image layers        | Use multi-stage or build secrets. Never COPY .env files.                |
| Health check always failing    | Verify health endpoint exists. Check port mapping.                      |
| Image vulnerability scan fails   | Update base image. Pin versions. Run trivy/grype in CI.                       |
| Container runs as root           | Add USER directive. Never run as root in production.                          |
| Docker Compose networking broken | Use service names, not localhost. Check network_mode and depends_on.         |

## 🚩 Red Flags / Anti-Patterns

- Using `latest` tag in production (non-reproducible)
- Running as root in production
- No .dockerignore (sends .git, node_modules to daemon)
- COPY . before installing dependencies (busts cache every time)
- No HEALTHCHECK (can't monitor container health)
- Installing dev dependencies in production image
- Using full Ubuntu/Debian when Alpine suffices
- Hardcoding secrets in Dockerfile

## Common Rationalizations

| Excuse                                  | Reality                                                    |
| --------------------------------------- | ---------------------------------------------------------- |
| "It's just for local dev"               | Local dev habits become production habits. Write it right. |
| "Alpine has compatibility issues"       | Test first. Most Node/Python apps work fine on Alpine.     |
| "Health check is overkill"              | Without health check, orchestrator can't detect crashes.   |
| "Running as root is fine in containers" | Container escapes exist. Defense in depth.                 |

## ✅ Verification Before Completion

```
1. Multi-stage build: build tools NOT in final image
2. Non-root user: `USER` directive present
3. HEALTHCHECK: instruction present and functional
4. Build succeeds: `docker build -t test .` exits 0
5. Container starts: `docker run test` stays running
6. Health check passes: `docker inspect --format='{{.State.Health.Status}}' <container>` = healthy
7. Image size reasonable: `docker images test` shows reasonable size
8. .dockerignore present and excludes unnecessary files
```

## 💰 Quality for AI Agents

- **Structured formats**: Headers + bullets > prose.
- **Cross-reference paths**: Write `skills/XX-name/SKILL.md` not vague references.

"No completion claims without fresh verification evidence."

## Examples

### Production Node.js Dockerfile

```dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:18-alpine
RUN apk add --no-cache tini && \
    addgroup -g 1001 appgroup && \
    adduser -u 1001 -G appgroup -s /bin/sh -D appuser

WORKDIR /app
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/package*.json ./
RUN npm ci --production

USER appuser

HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget --spider -q http://localhost:3000/health || exit 1

EXPOSE 3000
ENTRYPOINT ["tini", "--"]
CMD ["node", "dist/main.js"]
```

### Docker Compose for Dev

```yaml
services:
  app:
    build: .
    ports: ["3000:3000"]
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/mydb
    depends_on:
      db: { condition: service_healthy }
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: pass
    volumes: ["pgdata:/var/lib/postgresql/data"]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 5

volumes:
  pgdata:
```

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/k1lgor) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-14 -->

