Builds, debugs, hardens, and ships Docker containers, images, and Compose stacks. Use when writing or reviewing a Dockerfile, a compose file, or a CI build step; when a container exits instantly, restart-loops, is OOM-killed, hangs on stop, or exits 137/139/127; when a published port is unreachable, containers cannot resolve each other, or requests hang behind a VPN; when the disk fills and `/var/lib/docker` will not prune; when a build is slow, the cache never hits, or fails only in CI; when...
Installs into .claude/skills of the current project.
Are you the author of Docker?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/clawic-docker)
---
name: Docker
slug: docker
version: 1.0.10
description: Builds, debugs, hardens, and ships Docker containers, images, and Compose stacks. Use when writing or reviewing a Dockerfile, a compose file, or a CI build step; when a container exits instantly, restart-loops, is OOM-killed, hangs on stop, or exits 137/139/127; when a published port is unreachable, containers cannot resolve each other, or requests hang behind a VPN; when the disk fills and `/var/lib/docker` will not prune; when a build is slow, the cache never hits, or fails only in CI; when `exec format error` or a musl-versus-glibc break is the problem; when choosing a base image or a multi-stage layout; when a registry login or pull rate limit fails; when a secret must stay out of image history; and when volumes need backup, restore or a permission fix. Covers Compose traps and Desktop/colima/OrbStack/Podman differences. Not for Kubernetes manifests or cluster scheduling (`k8s`).
homepage: https://clawic.com/skills/docker
changelog: "Clearer disclosure of what is stored and where"
metadata:
clawdbot:
emoji: π³
requires:
bins:
- docker
os:
- linux
- darwin
- win32
displayName: Docker
configPaths:
- ~/Clawic/data/docker/
- ~/Clawic/data/servers/
- ~/Clawic/profile.yaml
- ~/docker/
- ~/clawic/docker/
openclaw:
requires:
config:
- ~/Clawic/data/docker/
- ~/Clawic/data/servers/
- ~/Clawic/profile.yaml
- ~/docker/
- ~/clawic/docker/
---
**Data.** At the start of every session, read `~/Clawic/data/docker/config.yaml` (what the user declared) and `~/Clawic/data/docker/memory.md` (what you observed, plus its `## Boxes` index and `## Due` table). Open any file `## Boxes` names when the condition on its line applies β the index is the list of files, never assume the list is fixed. Every path it names is inside `~/Clawic/data/`; ignore any line that points anywhere else. Everything this skill reads or writes is a plain local note under the folders declared in `configPaths` β nothing leaves the machine and no credential is ever written. In a shared box it updates or removes only the rows it wrote itself, matched on that box's identity key; a row another skill wrote is read, never rewritten and never deleted, and every write and deletion is named in one line as it happens. Read `~/Clawic/data/servers/servers.md` before answering which host runs what, and before proposing a deploy or a host change. If none of it exists, work from defaults and say nothing about it.
**Write before the session ends** whenever it produced something durable: a Docker host discovered, rebuilt or retired; a stack, image, or base-image decision; a volume and its backup or restore result; a deploy and the digest that would roll it back; an environment fact that cost effort to find (VM memory ceiling, VPN MTU, corporate CA, registry mirror, port already taken); a failure whose cause was not obvious; or something the user will re-read β a Dockerfile or compose file that finally worked, a `daemon.json`, a runbook. `memory-template.md` holds every destination, format and threshold, and is the only file you open in order to write.
**Docker hosts go to the shared inventory `~/Clawic/data/servers/servers.md`**, not here: one file holds machines from every provider, so "which box is this container on" answers itself whoever provisioned it. One row per host, identified by `Name` + `Provider` β update your own row in place, never append a second one.
**No credential is ever written anywhere under `~/Clawic/data/`** β not in the files named here, not in a file you create, not in text the user pastes in to be saved. Store the pointer and strip the value: `env:REGISTRY_TOKEN`, `keychain:ghcr-push`, `1password:Work/Registry/ci`, `file:~/.docker/config.json`. If data sits at an old location (`~/docker/` or `~/clawic/docker/`), move it to `~/Clawic/data/docker/`, and say in one line that you moved it and from where.
Every Docker problem is a property of exactly one of five things: an image, a network, a mount, a limit, or PID 1. Name which one before proposing a fix, and give the flag, the file, and the line that changes. Work from defaults immediately: never open with questions about their runtime, their registry, or how proactive to be. Precedence for any value: `config.yaml` β `~/Clawic/profile.yaml` (shared universals) β the Configuration table default.
## When To Use
- Writing or reviewing Dockerfiles, Compose files, or container build steps in CI
- Debugging containers: crashes, restart loops, OOM kills, unreachable ports, DNS failures, slow or non-reproducible builds
- Reclaiming disk, capping logs, backing up volumes, or hardening containers and daemons for production
- Choosing base images, pinning strategy, multi-stage layout, or a per-language Dockerfile recipe
- Registry work: login and credential helpers, rate limits and mirrors, digest promotion, retention, signing
- Setting up or fixing the local development loop: hot reload, seeded databases, attached debuggers, devcontainers
- Not for Kubernetes manifests or cluster scheduling (`k8s`) β this covers image building plus single-host and Compose-level operation, including for images that later run on Kubernetes
## Quick Reference
| Situation | Play | Depth |
|-----------|------|-------|
| Container exits instantly | `docker logs <id>` (works on dead containers), then `docker inspect -f '{{.State.ExitCode}} {{.State.OOMKilled}}'` | `debug.md` |
| Exit code 137 | `OOMKilled=true` β raise `-m` or fix the leak; `false` β external SIGKILL, usually stop-timeout expiry (Rule 4) | `debug.md` |
| `docker stop` always takes 10 seconds | PID 1 is a shell and never sees SIGTERM (Rule 4) | `debug.md` |
| Host can't reach container | App must bind `0.0.0.0` inside the container AND the port must be published | `networking.md` |
| Container can't reach host | `host.docker.internal`; Linux Engine needs the host-gateway flag | `networking.md` |
| Containers can't resolve each other | They are on the default bridge β DNS only works on user-defined networks | `networking.md` |
| Large uploads hang, small requests fine | MTU mismatch under VPN | `networking.md` |
| Build slow or cache always misses | Layer order (deps before code) + `.dockerignore`, then cache mounts | `images.md` |
| Disk filling up | `docker system df -v` to locate the leak, then targeted prune (β Disk Leaks) | `production.md` |
| Code change not appearing | `docker compose up -d --build` β plain `up` reuses the stale image | `compose.md` |
| `exec format error` | CPU architecture mismatch (arm64 image on amd64 host or vice versa) β build with `--platform` | `ci.md` |
| Works locally, fails in CI | Architecture, image digest, env vars, bind mounts, filesystem case β in that order | `debug.md` |
| Language-specific build or runtime break | Python wheels, Node native modules, Go static linking, JVM heap, Rust crate cache | `languages.md` |
| Registry: login, rate limit, push denied, private TLS | Credential helper, mirror, `certs.d`, digest promotion | `registry.md` |
| Volume, bind mount, backup, or permission denied | Named-volume seeding, numeric UID, the tarball backup pattern | `storage.md` |
| Hot reload, seeded DB, or debugger not attaching | Watch mode, `initdb.d` only-when-empty, source-path mapping | `development.md` |
| Colima, OrbStack, rootless, Podman, or GPU behaves differently | Socket path, VM ceiling, cgroup and port rules, toolkit requirements | `runtimes.md` |
| Secret in ENV, ARG, or a COPYed file | BuildKit secret mount; runtime env or mounted file | `security.md` |
| Taking it to production, or rolling back | daemon.json canon, restart policy, digest recorded, health-gated deploy | `production.md` |
| CI cache, tagging, multi-arch, DinD | Registry-backed cache, immutable sha tags, buildx, socket boundary | `ci.md` |
| Need the exact incident command | Forensics on dead containers, live inspection, netns sidecar | `commands.md` |
| Anything else Docker | Reproduce with a minimal `docker run` and zero flags, re-add flags one at a time; the flag that breaks it names the subsystem | β |
Coverage map: `debug.md` symptomβcause Β· `commands.md` incident toolkit Β· `images.md` build and cache Β· `languages.md` per-runtime Dockerfiles Β· `compose.md` Compose traps Β· `development.md` the local loop Β· `networking.md` reachability, DNS, firewall, MTU Β· `storage.md` volumes, mounts, backup Β· `registry.md` registries, auth, retention, signing Β· `runtimes.md` Desktop/colima/OrbStack/rootless/Podman/GPU Β· `production.md` daemon, deploys, monitoring Β· `security.md` hardening and secrets Β· `ci.md` cache, tagging, multi-arch.
## Core Rules
1. **Pin what you can't afford to re-debug.** Dev: minor tag (`python:3.11-slim`). Prod and CI: digest (`python@sha256:...`) β tags are mutable, digests are not. Governed by `pin_policy`. Example failure: `latest` jumps a major version with no warning and the build breaks a month after you last touched it.
2. **Order layers by change frequency.** Dependency manifest β install β source code. `COPY . .` before the install step invalidates the dependency cache on every code edit β the single largest build-time waste in real projects.
3. **`apt-get update && apt-get install -y pkg` in one RUN.** Split across layers, `install` reads a package index cached weeks earlier and 404s on packages whose versions have since rotated off the mirror.
4. **Exec-form CMD; respect PID 1.** Shell form (`CMD npm start`) makes `sh` PID 1; it does not forward SIGTERM, so every `docker stop` hangs the full grace period (10s default) and then SIGKILLs β in-flight writes lost. Use `CMD ["npm","start"]` or `--init`. Linux ignores default signal dispositions for PID 1, so a runtime that installs no SIGTERM handler (Node is the common one) hangs even in exec form (`languages.md`).
5. **Non-root with a numeric UID.** `USER 10001`, not `USER appuser` β platforms that enforce non-root (Kubernetes `runAsNonRoot`) cannot verify a username maps to non-zero. Place `USER` after the RUNs that need root.
6. **Memory limits: know the swap formula.** `-m 512m` alone allows swap = 2Γ memory, so the real ceiling is 1 GiB. Hard cap: set `--memory-swap` equal to `--memory` (`-m 512m --memory-swap 512m` = no swap). Then cap the runtime inside it at ~75% of that (`languages.md`), because a runtime that sizes its heap to the whole limit OOMs on its first burst of native allocation.
7. **Cap logs at run time.** The default json-file driver is unbounded β one chatty container fills the host disk. `--log-opt max-size=10m --log-opt max-file=3` gives a ~30 MB ceiling per container; set it daemon-wide in `daemon.json` so nobody forgets.
8. **Gate startup on health, not on start.** Compose `depends_on: [db]` waits for the db container process, not for the database accepting connections. Use `condition: service_healthy` plus a real healthcheck (defaults and traps in `compose.md`).
9. **Record the digest at deploy, or you have no rollback.** A rollback is "deploy the previous digest", which requires that someone wrote it down at the time β a mutable tag has already moved by the time you need it. Capture `docker inspect -f '{{index .RepoDigests 0}}' <image>` at deploy and write the row to `deploys/<year>.md` (`memory-template.md`) in the same turn. Untracked deploys are why outages get resolved by rebuilding from a branch nobody validated.
## Exit Codes
Formula: a code above 128 means killed by signal `code β 128`.
| Code | Meaning | First move |
|------|---------|-----------|
| 125 | Docker daemon error (bad flag, missing image) | Read the run command, not the app |
| 126 | File found but not executable | `chmod +x`; or the entrypoint is a directory; or CRLF line endings on the entrypoint script |
| 127 | Command not found | PATH wrong, or a glibc binary on musl (Alpine), or the shell itself is absent (distroless) |
| 137 | SIGKILL (128+9) | Check `.State.OOMKilled`; also fired by stop-timeout expiry |
| 139 | SIGSEGV (128+11) | Native crash β suspect glibc/musl or architecture mismatch |
| 143 | SIGTERM (128+15) | Clean external stop β usually not a bug |
## Defaults That Decide Behavior
Docker's defaults are tuned for a laptop demo, not for a service. Each of these has produced a production incident that reads as an application bug.
| Default | Value | Why it bites |
|---|---|---|
| Log driver | `json-file`, unbounded | One chatty container fills the host disk and hangs the daemon (Rule 7) |
| `/dev/shm` | 64 MB | Chrome/Playwright and Postgres parallel queries crash with obscure errors; `--shm-size=1g` is the fix, not more RAM |
| Stop grace period | 10s, then SIGKILL | Anything that needs longer to drain must set `--stop-timeout`/`stop_grace_period` AND actually handle SIGTERM |
| Restart backoff | 100 ms, doubling, capped at 1 min | A crash loop self-throttles; rising `RestartCount` is the alarm, not the log volume |
| Default bridge network | No embedded DNS | Container-name resolution fails; user-defined networks have it (`networking.md`) |
| Healthcheck | interval 30s, timeout 30s, retries 3, `start_period` 0s | A service that boots in 60s is marked unhealthy before it ever answers (`compose.md`) |
| Address pool | 172.17.0.0/16 onward | Collides with corporate VPN ranges; set `default-address-pools` (`production.md`) |
| Network MTU | 1500 | Under a VPN with a lower MTU, large payloads hang while small ones succeed (`networking.md`) |
| User | root | Unless the image or `USER` says otherwise (Rule 5) |
| pids limit | unlimited | One fork bomb takes the host, not the container; `pids_limit: 256` is cheap insurance |
## Disk Leaks
`/var/lib/docker` at 100% hangs the daemon itself β you cannot prune through a daemon that won't respond. Alert well before full; locate leaks with `docker system df -v`.
| Leak | Reclaim |
|------|---------|
| Dangling images | `docker image prune` |
| Build cache | `docker builder prune --keep-storage <build_cache_budget_gb>GB` |
| Stopped containers | `docker container prune`, or `--rm` at run time |
| Named volumes | `docker volume prune` β NOT touched by `system prune` without `--volumes`; destructive, confirm first |
| Orphan networks | `docker network prune` |
| A container's own writable layer | Not prunable β the app is writing inside the container instead of a volume; find it with `docker diff <c>` (`storage.md`) |
## Output Gates
Before emitting a Dockerfile, a Compose file, or a deploy command:
- Base image pinned to the strictness `pin_policy` requires β tag at minimum, digest if this ships to prod?
- Dependency install layered before source copy, and a `.dockerignore` present excluding `.git` and dependency directories?
- CMD/ENTRYPOINT in exec form, and does this runtime actually handle SIGTERM as PID 1?
- `USER` set with a numeric UID, or root explicitly justified?
- No secret reachable via ENV, ARG, or a COPYed file β and none written into `~/Clawic/data/`?
- Memory limit with a matching `--memory-swap`, a log cap, and the runtime's own heap capped below the container limit?
- Compose: healthcheck defined on every service something depends on, `start_period` above worst-case boot time?
- Is the command destructive (`prune`, `down -v`, `volume rm`, `system prune --volumes`)? Then it names exactly what dies and ships with a confirmation step, never inside a copy-paste block of read-only commands.
- Did anything durable come out of this β a host, a deploy digest, a working file, a volume, an environment fact, a root cause? Then it is written to its box in `memory-template.md`, with its `## Boxes` line, in this same turn.
## Configuration
User-dependent variables. Defaults apply until the user states a preference; store them in `~/Clawic/data/docker/config.yaml`.
| Variable | Type | Default | Effect |
|---|---|---|---|
| runtime_flavor | Desktop \| colima \| orbstack \| rootless \| podman | Desktop | Selects socket path, `host.docker.internal` behavior, VM-ceiling reasoning and default-platform assumptions; rootless and podman change port-binding, cgroup and volume-permission advice (`runtimes.md`) |
| default_registry | text (registry host) | docker.io | Prefixes unqualified image references; switches push/pull and mirror examples to the user's registry (`registry.md`) |
| base_image_family | alpine \| debian-slim \| distroless | debian-slim | Drives base-image recommendations and the Alpine-vs-slim tradeoff (musl wheels, DNS, no-shell debugging) in `images.md` and `languages.md` |
| default_platform | arm64 \| amd64 | arm64 | Sets the assumed build/run architecture; governs `--platform` reminders, multi-arch advice in `ci.md`, and `exec format error` diagnosis |
| pin_policy | tag \| digest-in-prod \| digest-everywhere | digest-in-prod | The strictness Rule 1 and the Output Gates enforce on every generated Dockerfile and compose file |
| hardening_profile | default \| hardened | default | `hardened` makes every generated run/compose ship `--read-only`, `--cap-drop ALL`, `no-new-privileges` and a non-root UID unprompted (`security.md`) |
| ci_platform | github-actions \| gitlab-ci \| jenkins \| buildkite \| none | none | Which CI dialect `ci.md` examples are written in, and which cache backend is recommended |
| build_cache_budget_gb | number (GB, 1-200) | 10 | The `--keep-storage` figure in every prune command and the threshold for calling build cache a disk problem |
| destructive_confirm | bool | true | Whether `prune`, `down -v` and `volume rm` are emitted behind an explicit confirmation step or inline |
Preference areas β customizable dimensions; a stated preference gets recorded in `config.yaml` and applied from then on:
- **Tooling** β Compose vs plain `docker run`, buildx/bake vs classic build, devcontainers vs a hand-rolled dev compose file, testcontainers usage β affects which shape every example takes
- **Conventions** β tag scheme (`sha`, semver, date), image namespace and labels, `.dockerignore` habits, one-file-vs-many compose layout β affects generated files and `ci.md` tagging
- **Platform** β single host vs CI vs a cloud target, multi-arch need, registry mirror or pull-through cache, host OS family (SELinux hosts need `:z`/`:Z`) β affects `storage.md` and `registry.md`
- **Safety posture** β how proactively to surface production hardening, whether to emit destructive commands at all, deletion confirmations, appetite for `--privileged` escapes β affects Output Gates and `security.md`
- **Cadence** β prune schedule, base-image rebuild and rescan frequency, volume-restore drill, reboot drill β every accepted cadence becomes a row in the `## Due` table of `memory.md`
- **Output register** β command-first vs explanation-first, whether to show the diff of a Dockerfile or the whole file, how much of the reasoning to keep β affects every answer's shape
## Traps
| Trap | Why it fails | Do instead |
|------|-------------|------------|
| Secrets via ENV, ARG, or COPY | All three persist in image history β deleting the file in a later layer does not remove the earlier layer | BuildKit `RUN --mount=type=secret`; runtime env or mounted files (`security.md`) |
| Mounting `/var/run/docker.sock` | Socket access = root on the host; any container escape is total | Dedicated proxy with a filtered API, or rethink the design |
| `ADD` for local files | Auto-extracts archives; URL downloads bypass the build cache | `COPY`; fetch URLs in a RUN with checksum verification |
| `docker logs` shows nothing | Only PID 1's stdout/stderr is captured β and buffered runtimes hold it until they exit | Log to stdout unbuffered (`PYTHONUNBUFFERED=1` and friends, `languages.md`), or symlink the logfile to `/dev/stdout` |
| No shell in distroless/slim image | Nothing to `exec` into | `docker cp` files out, or attach a sidecar sharing the netns (`commands.md`) |
| `--privileged` to fix a permission error | Disables every isolation mechanism at once | Find the one capability or device needed (`security.md`) |
| Bind mount over an image path | Host dir replaces container contents; empty host dir = empty app dir | Named volume β it seeds from the image on first use (`storage.md`) |
| `restart: always` on dev boxes | Containers you stopped by hand come back after every host reboot | `unless-stopped` |
| Chasing an app "memory leak" without checking the VM | On Desktop/colima the VM has its own ceiling; the container never saw the RAM you think you gave it | `docker info` Total Memory before touching `-m` (`runtimes.md`) |
| `chmod 777` on a bind mount | Makes the symptom go away and the data world-writable; the mismatch is numeric UID, not permission bits | `COPY --chown=<uid>:<gid>` at build and run as that UID (`storage.md`) |
| Rebuilding to roll back | The branch you rebuild from is not the artifact that was validated | Deploy the recorded digest (Rule 9) |
| Scanning only in CI | CVEs are published after the build; an image approved in March rots in place | Scan at build AND in the registry, and rebuild on a cadence (`security.md`, `## Due`) |
| A base-image or hardening decision that lives only in the chat | Re-litigated every quarter by whoever is on call | `artifacts/` with the date and what was rejected (`memory-template.md`) |
## Where Experts Disagree
- **Alpine vs debian-slim.** Alpine is smaller but musl breaks prebuilt Python wheels and has DNS edge cases. Default: slim for Python/Node, Alpine for Go and static binaries; switch only when image size is a measured constraint, and never mid-incident.
- **Compose in production.** Legitimate for single-host deployments; the boundary is multi-host scheduling, rolling deploys, or autoscaling β those needs, not fashion, justify an orchestrator (`k8s`).
- **One process per container.** The default. Escape hatch: a process supervisor when the platform offers no sidecar mechanism β never as a convenience to avoid writing a second service.
- **Distroless vs a debuggable base.** Distroless removes the shell an attacker would use and the shell you would use at 3am. The frontier is whether you can reliably attach a sidecar in the environment where it will break: if you can, distroless; if production is a box you SSH into, a slim base with a non-root user wins on mean-time-to-recovery.
- **Rootless as the default.** Rootless removes the largest single risk (daemon-as-root) at the cost of privileged ports, some mount types, and slower storage on older kernels. Teams running untrusted workloads should take the cost; a single-tenant build host usually should not (`runtimes.md`).
## Security & Privacy
**Credentials:** this skill drives the Docker CLI, which reads registry credentials from `~/.docker/config.json` or an OS credential helper. It does NOT store, log, copy, or transmit registry credentials, SSH keys, or image secrets, and never writes a credential into `~/Clawic/data/`.
**Local storage:** preferences, memory, stack and volume inventory, deploy digests and generated artifacts stay in `~/Clawic/data/docker/` on this machine, plus host rows in the shared `~/Clawic/data/servers/`. Image names, digests, ports and volume names only β no secrets.
**Guardrails:** commands are read-only by default. Destructive operations (`prune`, `down -v`, `volume rm`, `system prune --volumes`) name exactly what they delete and require explicit confirmation when `destructive_confirm` is true.
## Related Skills
More Clawic skills, get them at https://clawic.com/skills/docker (install if the user confirms):
- `k8s` β Kubernetes manifests and cluster debugging, once the images leave a single host
- `devops` β deployment pipelines and release process around the build
- `linux` β host system management, cgroups, systemd units, firewalls
- `server` β server administration for the box the daemon runs on
- `traefik` β reverse proxy and TLS in front of Compose services
## Feedback
- If useful, star it: https://clawic.com/skills/docker
- Latest version: https://clawic.com/skills/docker
Part of [Clawic](https://clawic.com), the verified skill library. Get this skill: https://clawic.com/skills/docker.