ソース情報
- リポジトリ
- cosmix/loom
- ソースの最終更新活動
- 2026年8月29日 21:19
- 検出された SKILL.md の言語
- 英語
- スター
- 61
- フォーク
- 4
インストール方法
デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。
ソースファイルを確認
インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。
SKILL.md を表示中
SKILL.md
ソースの指示 · 読み取り専用プレビュー- name
- loom-docker
- description
- Docker and container expertise.
- allowed-tools
- ["Read","Grep","Glob","Edit","Write","Bash"]
- triggers
- ["docker","container","dockerfile","image","compose","registry","build","layer","cache","multi-stage","volume","network","port","environment","containerize","orchestration","push","pull","tag","alpine","slim","distroless"]
# Docker
## Overview
Production-grade containers: Dockerfiles, compose, multi-stage builds, layer/cache optimization, security hardening, registries, debugging. The **Expert Practices** section below is the highest-value part — it states the *mechanism* behind each rule, which is what separates correct-but-naive Dockerfiles from production ones. Read it before writing anything non-trivial.
## Writing Dockerfiles
**Base image:** official images; `-alpine`/`-slim`/distroless/`scratch` for runtime; pin by tag AND digest (`python:3.12-slim@sha256:...`, see Currency), never `latest`; multi-stage to drop build toolchains.
**Layer ordering (cache):** least-changing first (base → system deps → dependency manifests → source). **Copy dependency manifests and install BEFORE copying source** so a code edit doesn't bust the dependency layer — this single ordering rule is the biggest cache win.
**Layer hygiene:**
- Keep `apt-get update` and `apt-get install` in the SAME `RUN` — in separate layers Docker reuses a stale cached `update` index when only the install list changes, silently installing outdated/vulnerable packages (docs call this "cache busting"). Details in Anti-Patterns.
- `--no-install-recommends`, clean caches in the same layer (`rm -rf /var/lib/apt/lists/*`).
- `.dockerignore` to keep context small (excludes `.git`, `node_modules`, build artifacts) — bloated context slows builds and busts cache.
**Runtime & security:** non-root numeric `USER uid:gid` (see Gotchas); `--read-only` root fs where possible; secrets via `RUN --mount=type=secret`, never `ENV`/`ARG`/`COPY` (see below); `HEALTHCHECK`; exec-form `CMD`/`ENTRYPOINT` (shell form breaks signals — see Anti-Patterns).
> **Why `ARG`/`ENV` are unsafe for secrets:** `ARG` (incl. `--build-arg`) and `ENV` are recorded in image metadata, visible via `docker history --no-trunc`, `docker inspect`, and provenance attestations to anyone who can pull; `ENV` is additionally readable at runtime via `/proc/self/environ`. Unsetting in a later `RUN` does NOT scrub it — already committed in the earlier layer. Use `RUN --mount=type=secret`. `ARG`/`ENV` are safe only for non-sensitive values (versions, build flags).
## Docker Compose
- **Deps:** `depends_on` with `condition:` (`service_healthy`, `service_completed_successfully`) — bare `depends_on` only orders start, doesn't wait for readiness.
- **Health:** `healthcheck` on every service other services wait on.
- **Restart:** `unless-stopped` / `on-failure` for production.
- **Networking:** named networks for isolation; databases/caches get no host port mapping; reference services by name (DNS); listen on `0.0.0.0` not `127.0.0.1`.
- **Volumes:** named volumes for data; bind mounts for dev code only.
- **Config:** `${VAR:-default}` interpolation; `secrets:` (file-backed) with `*_FILE` env convention, not plaintext env.
- **Limits:** `deploy.resources.limits` for prod.
## Security Hardening
- Minimal base (distroless/alpine) shrinks attack surface.
- Drop all Linux capabilities, add back only what's needed; pair with `no-new-privileges` (see Security).
- Non-root, read-only root, no `--privileged`.
- Scan in CI (trivy, grype, Docker Scout); keep bases current; generate SBOM + provenance (see Security).
- Secrets: never in Dockerfile/`ENV`/`ARG`; build-time `--mount=type=secret`; runtime via orchestrator secrets/vault.
## Debugging
```bash
docker logs -f --tail 100 <container> # or: docker-compose logs -f app
docker exec -it <container> /bin/sh # shell into running container
docker run --rm -it --entrypoint /bin/sh img # shell into a broken image
docker build --progress=plain --no-cache . # full build output
docker diff <container> # filesystem changes vs image
```
Tools: **dive** (layer bloat), **hadolint** (Dockerfile lint), **trivy**/**grype**/**Docker Scout** (CVEs), **ctop** (live resource monitor). For minimal/distroless images that lack a shell, debug via `docker run --entrypoint`, ephemeral debug containers (`kubectl debug`), or `docker cp` files out.
Common causes: permission-denied → volume ownership vs container UID; "name not found" → wrong network / used `localhost` instead of service name; missing shared libs → glibc-vs-musl (see Gotchas); container exits immediately → check `docker logs`, verify CMD/ENTRYPOINT executable and required env present.
## Language Notes
| Lang | Key points |
| --- | --- |
| Python | `python:3.x-slim` for glibc; `pip install --no-cache-dir`; wheel-cache in builder; `uv` for speed. |
| Node | Copy `package*.json` first; `npm ci` (reproducible) not `install`; ship prod deps via `npm ci --omit=dev` or `npm prune --omit=dev` AFTER build (bundler/TS are devDeps — see Currency); `node:alpine` for size. |
| Rust | `cargo chef` for dep caching; `--release` + `strip`; copy only the binary; `scratch`/distroless runtime; target musl for alpine/scratch. |
| Go | `golang:alpine` builder; `CGO_ENABLED=0` static binary; `scratch`/distroless runtime; copy only the binary + CA certs. |
## Examples
### Multi-stage Python (glibc, non-root, healthcheck)
```dockerfile
FROM python:3.12-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends build-essential \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt
FROM python:3.12-slim AS runtime
RUN groupadd --gid 1000 app && useradd --uid 1000 --gid app --create-home app
WORKDIR /app
COPY --from=builder /app/wheels /wheels
RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels
COPY --chown=1000:1000 . .
USER 1000:1000 # numeric UID:GID for Kubernetes runAsNonRoot
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "app:create_app()"]
```
### Node with BuildKit cache mounts + prune
```dockerfile
# syntax=docker/dockerfile:1
FROM node:20-alpine AS base
WORKDIR /app
FROM base AS build
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci # ALL deps: build needs devDeps
COPY . .
RUN npm run build && npm prune --omit=dev # strip devDeps after build
FROM base AS production
RUN apk update && apk upgrade && rm -rf /var/cache/apk/* \
&& addgroup -g 1001 -S nodejs && adduser -S app -u 1001
COPY --from=build --chown=1001:1001 /app/dist ./dist
COPY --from=build --chown=1001:1001 /app/node_modules ./node_modules
COPY --from=build --chown=1001:1001 /app/package.json ./
USER 1001:1001
EXPOSE 3000
ENV NODE_ENV=production
CMD ["node", "dist/server.js"]
```
### Rust (cargo chef → distroless)
```dockerfile
FROM rust:1.75-alpine AS chef
RUN apk add --no-cache musl-dev && cargo install cargo-chef
WORKDIR /app
FROM chef AS planner
COPY Cargo.toml Cargo.lock ./
COPY src ./src
RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json # cached dependency layer
COPY . .
RUN cargo build --release && strip target/release/myapp
FROM gcr.io/distroless/cc-debian12
COPY --link --from=builder /app/target/release/myapp /usr/local/bin/myapp
USER 65532:65532 # distroless "nonroot"; numeric so runAsNonRoot verifies it
EXPOSE 8080
ENTRYPOINT ["/usr/local/bin/myapp"]
```
### Go (static binary → scratch)
```dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /app
RUN apk add --no-cache ca-certificates tzdata
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags='-w -s' -o /app/bin/server ./cmd/server
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app/bin/server /server
USER 65534:65534 # scratch has no /etc/passwd — MUST be numeric
ENTRYPOINT ["/server"]
```
### Compose with secrets, healthcheck, limits
```yaml
services:
app:
build: { context: ., args: { APP_VERSION: "${APP_VERSION:-latest}" } }
ports: ["8080:8080"]
environment:
- DATABASE_URL_FILE=/run/secrets/db_url # *_FILE convention, not plaintext env
secrets: [db_url]
depends_on:
db: { condition: service_healthy } # waits for readiness, not just start
restart: unless-stopped
deploy:
resources:
limits: { cpus: "1.0", memory: 512M }
db:
image: postgres:16-alpine
volumes: [postgres_data:/var/lib/postgresql/data]
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets: [db_password]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myapp"]
interval: 10s
timeout: 5s
retries: 5
secrets:
db_url: { file: ./secrets/db_url.txt }
db_password: { file: ./secrets/db_password.txt }
volumes:
postgres_data:
```
### .dockerignore
```gitignore
.git
node_modules
__pycache__
*.pyc
.venv
dist
build
.env
.env.*
Dockerfile*
docker-compose*
*.log
.DS_Store
```
### Multi-arch build (buildx)
```dockerfile
FROM --platform=${BUILDPLATFORM} golang:1.22-alpine AS builder
ARG TARGETARCH TARGETOS # provided by buildx per target
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /app/bin/server ./cmd/server
FROM alpine:3.21 # pin runtime base (never latest); static binary could use scratch
COPY --from=builder /app/bin/server /server
ENTRYPOINT ["/server"]
```
```bash
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest --push .
```
## Expert Practices: Idioms, Anti-Patterns & Gotchas
High-signal guidance. Each item states the mechanism, not just the rule.
### Anti-Patterns
**Shell form silently breaks signals and drops CMD.** `CMD gunicorn ...` is wrapped as `/bin/sh -c '<string>'`, so `/bin/sh` becomes PID 1 and (docs) "does not pass signals" — your app never gets SIGTERM from `docker stop`, which waits the full timeout then SIGKILLs (no graceful shutdown, no error). A shell-form `ENTRYPOINT` additionally "ignores any CMD or docker run command line arguments" — a `CMD` beneath it is silently dropped. Use exec form; a setup wrapper must hand off PID 1 with `exec "$@"`.
```dockerfile
ENTRYPOINT ["gunicorn", "--bind", "0.0.0.0:8000", "app:create_app()"] # process IS PID 1
```
```sh
#!/bin/sh
set -e
do_setup
exec "$@" # execve replaces the shell so the app becomes PID 1
```
**Separate `apt-get update` / `install` layers install stale packages.** Docker caches a RUN by its literal command string, so a standalone `RUN apt-get update` is reused even after the upstream index moves; editing only the install line rebuilds against the stale index, silently reintroducing outdated/CVE-laden packages. Keep them in one RUN, pin versions as an extra cache-bust trigger, clean lists in the same layer.
```dockerfile
RUN apt-get update && apt-get install -y --no-install-recommends \
curl=7.88.* git \
&& rm -rf /var/lib/apt/lists/*
```
### Gotchas
**`set -o pipefail` is needed for piped RUNs — but default `/bin/sh` may lack it.** POSIX `sh` reports only the last command's exit code, so `RUN wget -O- url | sh` exits 0 even when `wget` fails, committing a broken layer. dash (Debian) and ash (Alpine) don't support `-o pipefail`; set it Dockerfile-wide via `SHELL`.
```dockerfile
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN wget -qO- https://example.com/install.sh | sh
```
**Use a numeric `USER` (uid:gid), not a username.** A username must resolve against `/etc/passwd`, which `scratch` and some distroless images lack — the container fails to start. It also breaks Kubernetes `runAsNonRoot: true`: the kubelet parses the image User field **as a number** and never reads `/etc/passwd`, rejecting a string user ("container has runAsNonRoot and image has non-numeric user"). Always `USER <uid>:<gid>` matching the uid/gid you created. Canonical numbers: `65534` = traditional `nobody`; distroless `nonroot` = `65532`.
**`COPY --link --chown` requires numeric UID:GID.** `--link` copies into an isolated layer rebased on an implicit `scratch` with no `/etc/passwd`, so a named user fails with `invalid user index: -1`. Worse, behavior varies by builder: the BuildKit `docker-container` driver errors while the legacy `docker` driver may silently fall back to UID 0. Use numeric `--chown` with `--link`.
**Re-declare `ARG` inside each stage — values reset at every `FROM`.** An `ARG` before the first `FROM` is global scope, usable only by `FROM` lines, NOT inside any stage; an in-stage `ARG` is inherited only by stages built `FROM` it. A reference to an undeclared `ARG` expands to an empty string with no error, so builds succeed with a blank value.
```dockerfile
ARG VERSION=3.12
FROM python:${VERSION}-slim AS builder
ARG VERSION # re-declare to use it inside the stage
RUN echo "Building with Python ${VERSION}"
```
GitHubで見るこの SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る