Generates hardened, multi-stage Dockerfiles with non-root users, minimal base images, and a .dockerignore, after auto-detecting the application stack. Use when containerizing an application for the first time, when the user asks for a Dockerfile, when migrating from a VM deployment, or when an existing Dockerfile runs as root, uses a fat base image, or leaks build tooling into the runtime layer.
Install with Tessl CLI
npx tessl i github:santosomar/general-secure-coding-agent-skills --skill containerization-assistant97
Quality
96%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Produce a Dockerfile that is minimal, reproducible, runs as non-root, and separates build tooling from the runtime image. Then produce a .dockerignore that keeps the build context small.
Check the repo root for these markers, in order. First match wins.
| Marker file(s) | Stack | Builder stage base | Runtime stage base |
|---|---|---|---|
package.json + tsconfig.json | Node (TS) | node:<ver>-slim | gcr.io/distroless/nodejs<ver>-debian12 |
package.json only | Node (JS) | node:<ver>-slim | gcr.io/distroless/nodejs<ver>-debian12 |
go.mod | Go | golang:<ver>-alpine | gcr.io/distroless/static-debian12 (or scratch if CGO_ENABLED=0) |
Cargo.toml | Rust | rust:<ver>-slim | gcr.io/distroless/cc-debian12 |
pom.xml / build.gradle | Java (JVM) | eclipse-temurin:<ver>-jdk | eclipse-temurin:<ver>-jre (or distroless java) |
pyproject.toml / requirements.txt / Pipfile | Python | python:<ver>-slim | python:<ver>-slim (distroless python is viable if no C extensions) |
*.csproj / *.sln | .NET | mcr.microsoft.com/dotnet/sdk:<ver> | mcr.microsoft.com/dotnet/aspnet:<ver> |
Gemfile | Ruby | ruby:<ver>-slim | ruby:<ver>-slim |
Pull <ver> from the version pin in the marker file (engines.node, go 1.x, python_requires, etc.). If no pin exists, ask the user — never guess a major version.
If no marker matches, stop and ask: "What runtime does this application use?"
Before choosing the runtime base, scan for packages that compile C extensions or link system libraries:
psycopg2 (not -binary), cryptography, pillow, numpy, lxml, grpciosharp, canvas, sqlite3, bcrypt (not bcryptjs), anything with node-gyp in its install lognokogiri, pg, ffiIf found: the runtime stage needs libc/libstdc++/specific .so files. Switch from distroless/static → distroless/cc, or from scratch → alpine + apk add the runtime libs, or stay on -slim with the specific apt-get install --no-install-recommends <libs>.
Always multi-stage. Builder stage has compilers and dev deps; runtime stage has the binary/bundle and nothing else.
# syntax=docker/dockerfile:1.6
# ── Build stage ───────────────────────────────────────────
FROM node:20.11-slim@sha256:<digest> AS builder
WORKDIR /app
# Deps layer — cache hits when only src changes
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
RUN npm prune --omit=dev
# ── Runtime stage ─────────────────────────────────────────
FROM gcr.io/distroless/nodejs20-debian12@sha256:<digest>
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder --chown=nonroot:nonroot /app/dist ./dist
COPY --from=builder --chown=nonroot:nonroot /app/node_modules ./node_modules
COPY --from=builder --chown=nonroot:nonroot /app/package.json ./
USER nonroot
EXPOSE 3000
CMD ["dist/server.js"]# syntax=docker/dockerfile:1.6
FROM golang:1.22-alpine@sha256:<digest> AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags='-s -w' -o /out/app ./cmd/server
FROM gcr.io/distroless/static-debian12@sha256:<digest>
COPY --from=builder /out/app /app
USER nonroot
EXPOSE 8080
ENTRYPOINT ["/app"]# syntax=docker/dockerfile:1.6
FROM python:3.12-slim@sha256:<digest> AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
FROM python:3.12-slim@sha256:<digest>
RUN groupadd -r app && useradd -r -g app app
WORKDIR /app
COPY --from=builder /install /usr/local
COPY --chown=app:app . .
USER app
EXPOSE 8000
CMD ["python", "-m", "myapp"]FROM node:20-slim@sha256:abc.... Get the digest with docker buildx imagetools inspect node:20-slim. If the user can't provide digests at generation time, pin the tag and add a # TODO: pin digest comment — never leave it at latest.nonroot (uid 65532). For other bases, create one: RUN useradd -r -u 10001 app → USER app.COPY package.json package-lock.json ./ → RUN npm ci → COPY . .. This preserves the cache when only source changes.npm ci not npm install. pip install -r requirements.txt with a lockfile. go mod download before go build.ENV API_KEY=..., no COPY .env. If a secret is needed at build time, use BuildKit mounts: RUN --mount=type=secret,id=npmrc npm ci.HEALTHCHECK CMD curl -f http://localhost:3000/health || exit 1 requires curl in the image — which distroless doesn't have. Either ship a tiny static healthcheck binary or rely on orchestrator probes (K8s liveness) instead.If absent, always generate. Default:
.git
.gitignore
**/.DS_Store
**/node_modules
**/__pycache__
**/*.pyc
**/target
**/.venv
**/dist
**/build
**/coverage
.env
.env.*
**/*.log
Dockerfile*
.dockerignore
README*
*.mdThen add back with ! any build-time paths the Dockerfile actually COPYs. Example: if the Dockerfile does COPY dist ./dist (pre-built outside Docker), you need !dist at the end.
docker build . succeeds with no warnings about missing COPY sourcesdocker run --rm <img> id -u prints a non-zero UIDdocker history <img> shows no layer containing .git, .env, or node_modules in a non-Node imagedocker image ls — final image < 200 MB for Go/Rust, < 400 MB for Node/Python. If larger, something leaked from the builder.apt, apk, pip, npm, gcc in the runtime stage (run docker run --rm <img> which apt — should fail)COPY filters or a .dockerignore negation for just the needed packages. Consider turborepo/nx prune if available.COPYd to the runtime stage or it will crash on start. Grep for readFile, open(, __dirname, os.path.join(os.path.dirname(__file__) to find them./app. Mount a volume, or mkdir /data && chown app:app /data in the runtime stage and point the app at /data.--init to docker run, or use tini/dumb-init as ENTRYPOINT, or ensure the app itself handles SIGTERM.RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci. Never COPY .npmrc — the token ends up in a layer forever.FROM ubuntu / FROM debian for a single-binary app. That's ~80 MB of attack surface you don't run.COPY . . as the first COPY. Destroys layer caching — every source edit reinstalls every dependency.USER root in the final stage just because a package install needed it. Do root work earlier, drop privileges last.apt-get upgrade in the Dockerfile. Pin a newer base image instead — upgrades in-place are non-reproducible.EXPOSE as a security boundary. It's documentation. The orchestrator decides what's actually published.47d56bb
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.