Container development with Docker, Dockerfiles, 12-factor principles, multi-stage builds, and Skaffold workflows...
Expert knowledge for containerization and orchestration with focus on security-first, lean container images and 12-factor app methodology.
| Use this skill when... | Use a language-specific sibling (go-containers, nodejs-containers, python-containers) instead when... |
|---|---|
| Writing or optimizing language-agnostic Dockerfiles | Optimizing Go static binaries, Node.js Alpine builds, or Python slim images |
| Authoring multi-stage build patterns or 12-factor configuration | The image-size goal is dominated by language runtime choices (scratch, distroless, musl/glibc) |
| Hardening containers (non-root, minimal base, secrets) | Running Skaffold sync (skaffold-filesync) or OrbStack networking (skaffold-orbstack) |
| Composing services with Docker Compose | The work is purely a Skaffold pre-deploy test (skaffold-testing) |
Non-Root is MANDATORY: ALL production containers MUST run as non-root users. This is not optional.
Minimal Base Images: Use Alpine (~5MB) for Node.js/Go/Rust. Use slim (~50MB) for Python (musl compatibility issues with Alpine).
Multi-Stage Builds Required: Separate build and runtime environments. Build tools should NOT be in production images.
Container Image Construction
Container Orchestration
.dockerignore file1. Multi-Stage Builds (MANDATORY):
2. Minimal Base Images:
3. Non-Root Users (MANDATORY):
4. .dockerignore (MANDATORY):
.git, node_modules, __pycache__5. Layer Optimization:
&&CRITICAL: Before using base images, verify latest versions:
Use WebSearch or WebFetch to verify current versions.
For detailed language-specific optimization patterns, see the dedicated skills:
| Language | Skill | Key Optimization | Typical Reduction |
|---|---|---|---|
| Go | go-containers |
Static binaries, scratch/distroless | 846MB → 2.5MB (99.7%) |
| Node.js | nodejs-containers |
Alpine, multi-stage, npm/yarn/pnpm | 900MB → 100MB (89%) |
| Python | python-containers |
Slim (NOT Alpine), uv, venv | 1GB → 100MB (90%) |
Choose the right base image:
scratch or distroless/static (2-5MB)node:XX-alpine (50-150MB)python:XX-slim (80-120MB) - Never use Alpine for Python!nginx:XX-alpine (20-40MB)scratch or nginx:alpine (minimal)# Build stage - includes all build tools
FROM <language>:<version> AS builder
WORKDIR /app
# Copy dependency manifests first (better caching)
COPY package.json package-lock.json ./ # or go.mod, requirements.txt, etc.
# Install dependencies
RUN <install-command>
# Copy source code
COPY . .
# Build application
RUN <build-command>
# Runtime stage - minimal
FROM <minimal-base>
WORKDIR /app
# Create non-root user
RUN addgroup --gid 1001 appgroup && \
adduser --uid 1001 --gid 1001 --disabled-password appuser
# Copy only what's needed from builder
COPY --from=builder --chown=appuser:appuser /app/dist ./dist
USER appuser
EXPOSE <port>
HEALTHCHECK --interval=30s CMD <health-check-command>
CMD [<start-command>]
Security Requirements (Mandatory)
node:20.10-alpine), never latestTypical Impact of Full Optimization:
12-Factor App Principles
Container labels provide metadata for image discovery, linking, and documentation. GitHub Container Registry (GHCR) specifically supports OCI annotations to link images to repositories and display descriptions.
| Label | Purpose | Example |
|---|---|---|
org.opencontainers.image.source |
Links image to repository (enables GHCR features) | https://github.com/owner/repo |
org.opencontainers.image.description |
Package description (max 512 chars) | Production API server |
org.opencontainers.image.licenses |
SPDX license identifier (max 256 chars) | MIT, Apache-2.0 |
| Label | Purpose | Example |
|---|---|---|
org.opencontainers.image.version |
Semantic version | 1.2.3 |
org.opencontainers.image.revision |
Git commit SHA | abc1234 |
org.opencontainers.image.created |
Build timestamp (RFC 3339) | 2025-01-19T12:00:00Z |
org.opencontainers.image.title |
Human-readable name | My Application |
org.opencontainers.image.vendor |
Organization name | Forum Virium Helsinki |
org.opencontainers.image.url |
Project homepage | https://example.com |
org.opencontainers.image.documentation |
Documentation URL | https://docs.example.com |
# Static labels (set at build time)
LABEL org.opencontainers.image.source="https://github.com/owner/repo" \
org.opencontainers.image.description="Production API server" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.vendor="Forum Virium Helsinki"
# Dynamic labels (via build args)
ARG VERSION=dev
ARG BUILD_DATE
ARG VCS_REF
LABEL org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.created="${BUILD_DATE}" \
org.opencontainers.image.revision="${VCS_REF}"
docker build \
--label "org.opencontainers.image.source=https://github.com/owner/repo" \
--label "org.opencontainers.image.description=My container image" \
--label "org.opencontainers.image.licenses=MIT" \
--build-arg VERSION=1.2.3 \
--build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
--build-arg VCS_REF=$(git rev-parse --short HEAD) \
-t myapp:1.2.3 .
The docker/metadata-action automatically generates OCI labels from repository metadata:
- id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
labels: |
org.opencontainers.image.title=My Application
org.opencontainers.image.description=Production API server
org.opencontainers.image.vendor=Forum Virium Helsinki
- uses: docker/build-push-action@v6
with:
labels: ${{ steps.meta.outputs.labels }}
Auto-generated labels by metadata-action:
org.opencontainers.image.source (from repository URL)org.opencontainers.image.revision (from commit SHA)org.opencontainers.image.created (build timestamp)org.opencontainers.image.version (from tags/refs)Skaffold Preference
When building and testing containers, use these optimizations for faster feedback:
| Context | Command | Purpose |
|---|---|---|
| Quick build | DOCKER_BUILDKIT=1 docker build --progress=plain -t app . |
BuildKit with plain output |
| Build with cache | docker build --cache-from app:latest -t app:new . |
Reuse layers from previous builds |
| Security scan | docker scout cves app:latest | head -50 |
Quick vulnerability check |
| Size analysis | docker images app --format "{{.Size}}" |
Check image size |
| Layer inspection | docker history app:latest --human --no-trunc |
Analyze layer sizes |
| Build without cache | docker build --no-cache --progress=plain -t app . |
Force clean build |
| Test container | docker run --rm -it app:latest /bin/sh |
Interactive testing |
| Quick health check | docker run --rm app:latest timeout 5 /health |
Verify startup |
Build optimization flags:
--target=<stage>: Build specific stage only (faster iteration)--build-arg BUILDKIT_INLINE_CACHE=1: Enable inline cache--secret id=key,src=file: Mount secrets without including in imageFor detailed Dockerfile optimization techniques, orchestration patterns, security hardening, and Skaffold configuration, see REFERENCE.md.
Language-Specific Container Optimization:
go-containers - Go static binaries, scratch/distroless (846MB → 2.5MB)nodejs-containers - Node.js Alpine patterns, npm/yarn/pnpm (900MB → 100MB)python-containers - Python slim (NOT Alpine), uv/poetry (1GB → 100MB)/configure:container - Comprehensive container infrastructure validation/configure:dockerfile - Dockerfile-specific configuration/configure:workflows - GitHub Actions including container builds/configure:skaffold - Kubernetes development configuration