Docker logo

Docker

Community
ericrisco
docker

Use when authoring or auditing a Dockerfile, shrinking a bloated image, hardening a container that runs as root, picking a base image, or wiring a Compose dev loop with hot reload. NOT CI builds or deploy-to-host (that is `deployment`), NOT k8s autoscaling (that is `scaling`), NOT app-level injection or secrets-in-code (that is `secure-coding`).

Overview

Publisherericrisco
Repositoryrsc-harness
Skill namedocker
Stars
239
Forks
24
Bundled files
4
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 4 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by ericrisco on GitHub. Read the source before you install it.

Installation

Install the Docker AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/ericrisco/rsc-harness/tree/main/skills/docker
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/ericrisco/rsc-harness.git /tmp/rsc-harness
mkdir -p .claude/skills
cp -r /tmp/rsc-harness/skills/docker .claude/skills/docker
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Docker in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Docker on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Docker is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

docker — container-craft

You own one question, deeply: how do I turn this app into a small, secure, fast-building image — and run a frictionless local stack with Compose? Your artifacts are the Dockerfile, the .dockerignore, and the compose.yaml: their layers, their attack surface, their build cache, their dev loop. You do not touch what happens after the image is built.

text
source  →  Dockerfile (multi-stage, cache mounts)  →  small + scanned image  +  compose.yaml (dev loop)
                                                            └──────────────┬──────────────┘
                                                    hand off to ../deployment/SKILL.md

The hand-off is sharp: the moment you have a green, scanned image and a working compose.yaml, you are done. Everything downstream is ../deployment/SKILL.md — it already references Dockerfiles and leans on you for the image internals.

When NOT to use — and who owns it instead

You're asked for…OwnerWhy it's not you
CI build, registry push, deploy to host, rollback../deployment/SKILL.mdYou build + harden the image; it moves the image through CI to prod
k8s manifests, HPA autoscaling, ingressscalingOrchestration above a single image
App SQL injection, secrets in source, dep CVE triage../secure-coding/SKILL.mdYou cover only image/container hardening
Install/operate a self-hosted PaaS../coolify/SKILL.mdHost operation, not the image
Containerless PaaS deploy mechanics../railway/SKILL.md, ../fly-io/SKILL.mdPlatform deploy, not the Dockerfile

Pick a base image (2026 reality)

BaseSizeCVE / patch velocityDebuggable?Wins when
*-slim (e.g. bookworm-slim)~30–80 MBmoderate, glibcyes (shell, apt)pragmatic default, native deps / Python wheels
distroless (gcr.io/distroless/*)~2–25 MBconvenient but patches slowerno shellstatic/compiled runtimes, want minimal surface
Chainguard / Wolfitiny, glibclowest live CVE count, SLSA L3 attestationsminimalsecurity-first; real scans found high-sev CVEs where the Chainguard equivalent had zero
alpine~5 MB, muslsmall surfaceyes (apk)tiny static services — but musl breaks many Python wheels / native deps
scratch0nothing to patchnoa fully static binary (Go), nothing else

Rule of thumb: glibc app with native deps → *-slim or Wolfi. Static Go binary → scratch or distroless/static. Security mandate → Chainguard. Reach for alpine only when you've confirmed your wheels/native libs build against musl. Per-language tag maps, multi-stage templates for the stacks not shown below (Rust, JVM jlink, static SPA → nginx) and multi-arch buildx live in references/base-images-and-stages.md.

Multi-stage skeletons

Node — npm ci with a cache mount → distroless nonroot. Bad: single-stage node:20 ≈ 1.1 GB. Good: this ≈ 180 MB. Tags track Node 24 (the active LTS as of mid-2026); Node 22 is in Maintenance LTS, so swap 24→22 only when you deliberately want the conservative maintenance line.

dockerfile
# syntax=docker/dockerfile:1
FROM node:24-bookworm-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
RUN npm run build && npm prune --omit=dev

FROM gcr.io/distroless/nodejs24-debian13:nonroot
WORKDIR /app
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
USER nonroot
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s CMD ["node", "dist/healthcheck.js"]
CMD ["dist/server.js"]

Python — uv into a venv → slim, non-root. Bad: python:3.12 + pip ≈ 1 GB. Good: ≈ 130 MB.

dockerfile
# syntax=docker/dockerfile:1
FROM python:3.13-slim-bookworm AS build
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app
ENV UV_PROJECT_ENVIRONMENT=/opt/venv
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-dev

FROM python:3.13-slim-bookworm
RUN useradd --create-home --uid 10001 app
COPY --from=build --chown=app:app /opt/venv /opt/venv
COPY --chown=app:app . /app
WORKDIR /app
ENV PATH="/opt/venv/bin:$PATH"
USER app
EXPOSE 8000
HEALTHCHECK CMD ["python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/health').status==200 else 1)"]
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Go — static binary → scratch. Bad: golang:1.26 ≈ 900 MB. Good: ≈ 12 MB. Pin a supported toolchain: Go maintains only the latest two minors (1.26/1.25 as of mid-2026), so a golang:1.23 build image is two minors past EOL and no longer gets security patches.

dockerfile
# syntax=docker/dockerfile:1
FROM golang:1.26-bookworm AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download
COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /app ./cmd/server

FROM scratch
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /app /app
USER 10001:10001
EXPOSE 8080
ENTRYPOINT ["/app"]

The cache-mount target differs per ecosystem: /root/.npm, /root/.cache/uv (or /root/.cache/pip), /go/pkg/mod. The cache persists across builds without bloating any layer.

.dockerignore — load-bearing, not optional

Without it the entire context (including .git, node_modules, .env) ships to the daemon: it slows the build and can bake secrets into a layer via COPY ..

gitignore
.git
.gitignore
node_modules
dist
.env
.env.*
*.log
.venv
__pycache__
Dockerfile
.dockerignore
README.md

Mirror .gitignore, then add build outputs and the Dockerfile itself.

Harden the runtime

Non-root in the image (already shown above): create a user, COPY --chown, USER. Then lock the runtime down where the container actually runs:

bash
docker run \
  --read-only --tmpfs /tmp \
  --security-opt=no-new-privileges \
  --cap-drop=ALL \
  myimage:1.4.0

--read-only rootfs + --tmpfs /tmp for scratch space; no-new-privileges blocks setuid escalation; --cap-drop=ALL then add back only what you truly need (--cap-add=NET_BIND_SERVICE to bind port < 1024).

Build secrets — never ARG/ENV. An ARG TOKEN lands in the image history and any ENV SECRET=... persists in a layer. Use a BuildKit secret mount; it is never written to a layer:

dockerfile
# Bad — leaks into image history:  ARG NPM_TOKEN
# Good:
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN="$(cat /run/secrets/npm_token)" npm ci
bash
docker build --secret id=npm_token,env=NPM_TOKEN .

Compose for dev

Canonical filename is compose.yaml; omit the obsolete version: key; use docker compose (v2 subcommand), never the standalone docker-compose v1. One concern per container — the app, the database and the seed job are separate services. develop.watch is GA since Compose 2.22.0 and gives you a tight dev loop.

yaml
services:
  api:
    build: .
    ports: ["8000:8000"]
    depends_on:
      db:
        condition: service_healthy
    develop:
      watch:
        - action: sync          # hot-reload: copy changed files into the container
          path: ./src
          target: /app/src
        - action: rebuild       # lockfile / compiled langs: rebuild the image
          path: ./uv.lock
        - action: sync+restart  # config change: sync then restart the process
          path: ./config.yaml
          target: /app/config.yaml
  db:
    image: postgres:17-bookworm
    environment:
      POSTGRES_PASSWORD: dev
    volumes: ["pgdata:/var/lib/postgresql/data"]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5
  seed:
    profiles: ["tools"]         # only runs with: docker compose --profile tools up
    build: .
    command: ["python", "-m", "app.seed"]

volumes:
  pgdata:

Run the dev loop with docker compose watch (or docker compose up --watch). Fuller patterns — watch-per-stack, multi-service healthcheck graphs, override files, env_file vs secrets, and when to graduate to deployment/scaling — are in references/compose-recipes.md.

Verify the image

These are exactly what scripts/verify.sh automates (it skips a tool gracefully if absent):

  • hadolint Dockerfile — static lint of the Dockerfile (no error-level findings).
  • docker compose config -q — the compose.yaml parses and resolves.
  • trivy image myimage:1.4.0 — CVE + misconfig + secret scan of the built image.
  • trivy config . — scan the Dockerfile/compose for misconfiguration without a build.
  • dockle myimage:1.4.0 — CIS-style image hygiene.
  • size check — assert the final image is under your threshold (docker image inspect -f '{{.Size}}').

Anti-patterns

BadGoodWhy
FROM node:latestFROM node:24-bookworm-slim, or pin a digest:latest drifts under you; builds + scans become non-reproducible
runs as root (no USER)USER nonroot / USER 10001PID 1 defaults to root (UID 0), so a container escape == host compromise
ARG TOKEN / ENV SECRET=RUN --mount=type=secret,...ARG/ENV persist in image history & layers
COPY . . then RUN npm cicopy lockfile → install → copy sourcesource edits bust the dep cache layer every build
single FROM, ship the SDKmulti-stage, copy only the artifactruntime carries compilers & dev CVE surface
apt-get install xapt-get install --no-install-recommends x && rm -rf /var/lib/apt/lists/*recommends + apt lists bloat the layer
no .dockerignoremirror .gitignore + build outputswhole context ships; .env/.git can leak
version: "3.8" / docker-compose v1omit version:; docker compose v2the key is obsolete; v1 is EOL
CMD npm start (shell form)CMD ["node", "server.js"] (exec)shell form wraps the process in /bin/sh -c, which swallows SIGTERM — no graceful shutdown
no HEALTHCHECKHEALTHCHECK CMD [...]nothing for Compose/orchestrators to gate readiness on (condition: service_healthy)

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Docker AI skill do?

Use when authoring or auditing a Dockerfile, shrinking a bloated image, hardening a container that runs as root, picking a base image, or wiring a Compose dev loop with hot reload. NOT CI builds or deploy-to-host (that is `deployment`), NOT k8s autoscaling (that is `scaling`), NOT app-level injection or secrets-in-code (that is `secure-coding`).

Why use Docker on TypingMind?

Because you install it once and use it with any model. Docker is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Docker in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/ericrisco/rsc-harness/tree/main/skills/docker. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Docker?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Docker?

As many as you like. As long as a model supports skills, you can use Docker with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Docker AI skill free?

Yes. It is published on GitHub by ericrisco under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇