Install
$ agentstack add skill-hatch3r-hatch3r-hatch3r-containerize ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Containerization Workflow
Companion workflow to the hatch3r-devops agent: that agent reviews and authors infrastructure across CI/CD, IaC, and orchestration with apply-step gating; this skill is the focused step-by-step procedure for producing a hardened container image and its local + orchestration manifests. Use this skill when the task is "containerize this service" or "harden this Dockerfile"; escalate to the agent when the work spans pipelines, cloud IaC, or a deployment strategy.
Quick Start
Task Progress:
- [ ] Step 0: Detect ambiguity (P8 B1)
- [ ] Step 1: Pick a minimal base image + plan build/runtime split
- [ ] Step 2: Write the multi-stage Dockerfile
- [ ] Step 3: Harden — non-root USER, dropped privileges, HEALTHCHECK
- [ ] Step 4: Add .dockerignore + verify build context size
- [ ] Step 5: Author docker-compose for local dev
- [ ] Step 6: Author Kubernetes manifest with securityContext
- [ ] Step 7: Scan the image and gate on findings
Two invariants bound every image this workflow produces: it runs as a non-root user, and it ships only runtime artifacts (no build toolchain, no secrets). Steps 2–3 establish both; Step 7 verifies them.
Step 0 — Detect Ambiguity (P8 B1)
Before any work, scan the invocation for unresolved questions in scope, intent, acceptance criteria, target environment, or irreversibility. If any are found, ask the user via the platform-native question tool per agents/shared/user-question-protocol.md. Do not proceed under silent assumption. Default path, not an exception. Triggers for THIS skill: language/runtime + version (drives base-image choice), whether the target is local-dev-only or production orchestration, the orchestrator (plain Docker vs docker-compose vs Kubernetes), whether secrets are needed at build time vs runtime (build-time secrets are an irreversible leak risk), and the registry the final image pushes to.
Fan-out Discipline (P8 B2)
Fan-out scales with task size; token cost never justifies serializing independent work (rules/hatch3r-fan-out-discipline.md P8 B2; agents/shared/efficiency-patterns.md). Tier boundaries for THIS skill:
- Tier 1 (one service, Dockerfile only): inline.
- Tier 2 (Dockerfile + compose + one K8s manifest for one service): spawn one sub-agent per artifact via the Task tool.
- Tier 3 (multi-service repo, one image set per service): one fresh sub-agent per service; orchestrator integrates the compose/manifest set only.
Emit sub_agents_spawned: { count, rationale } in your output.
Step 1: Pick a Minimal Base Image and Plan the Build/Runtime Split
- Choose the smallest base that runs the app: a
-slimvariant, a distroless image, or Alpine where the libc difference is acceptable. A smaller base shrinks attack surface and pull time (Docker best-practices — see References). - Plan two stages: a build stage that carries the full toolchain (compilers, dev dependencies, test runners) and a runtime stage that carries only the built artifact plus its runtime dependencies (Docker multi-stage — see References).
- Pin the base image to a specific tag or digest (
node:22.5.1-slim, notnode:latest). A mutable tag makes the build non-reproducible.
Step 2: Write the Multi-Stage Dockerfile
Structure the Dockerfile so each FROM begins a stage and the final stage copies only the runtime output:
# --- build stage ---
FROM node:22.5.1-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --omit=dev
# --- runtime stage ---
FROM node:22.5.1-slim AS runtime
WORKDIR /app
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
- Order instructions stalest-to-freshest: copy the lockfile and install dependencies before copying source, so a code edit does not bust the dependency-install layer cache.
COPY --from=buildonly the artifacts the app needs at runtime — never the source tree, build cache, or dev dependencies.- Never write a secret into a layer. Use BuildKit build secrets (
RUN --mount=type=secret,...) for build-time credentials and runtime environment injection for runtime credentials; a secret in any layer is recoverable from the image (OWASP Docker Security — see References).
Step 3: Harden — Non-Root, Dropped Privileges, Healthcheck
- Create a dedicated unprivileged user with an explicit UID/GID and switch to it before the run command. An explicit UID survives rebuilds and maps to a Kubernetes
runAsUser(Docker best-practices — see References):
RUN groupadd -r app --gid=10001 && useradd -r -g app --uid=10001 app
USER app
- Running as root is the most common dangerous container misconfiguration: a container escape inherits host root (OWASP Docker Security — see References). The non-root
USERline is mandatory output of this step. - Add a
HEALTHCHECKso the orchestrator can detect and restart an unhealthy container — this is CIS Docker Benchmark control 4.6 (CIS — see References):
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD node ./dist/healthcheck.js || exit 1
- Do not install
sudo; its TTY and signal-forwarding behavior is unpredictable. If privilege elevation is unavoidable, usegosu(Docker best-practices — see References). - Use
COPYnotADD(ADD's URL-fetch and auto-extract behavior is a footgun); setENV NODE_ENV=production(or the runtime equivalent) so the app does not load dev tooling.
Step 4: Add .dockerignore and Verify Build Context Size
- Author a
.dockerignoreexcluding.git,node_modules(rebuilt in-image),.env*, test artifacts, CI config, and local build output. This keeps secrets out of the build context and shrinks the upload sent to the daemon. - A
.envfile reaching the build context is a credential-leak path even if a layer does not copy it — exclude it explicitly. - Verify: run the build and confirm the "transferring context" size is small (kilobytes, not the whole repo). A large context means the
.dockerignoremissed something.
Step 5: Author docker-compose for Local Dev
- Write a
docker-compose.yml(orcompose.yaml) that builds the image and wires backing services (database, cache, queue) for local development. - Inject configuration via
environment:/env_file:referencing a gitignored.env; never inline secrets in the compose file. - Add a
healthcheck:per service and usedepends_on:withcondition: service_healthyso the app waits for its database to be ready. - Pin backing-service images to specific tags, mirroring the Dockerfile base-pin policy.
- Mount source as a volume only in the local-dev compose, never in the production image build.
Step 6: Author the Kubernetes Manifest with securityContext
- Author a Deployment + Service. Set a
securityContextthat enforces the Dockerfile hardening at the orchestrator layer (Kubernetes hardening — see References):
securityContext:
runAsNonRoot: true
runAsUser: 10001
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
- Set resource
requestsandlimits(CPU + memory) so a runaway container cannot starve the node. - Map the Dockerfile
HEALTHCHECKto areadinessProbeandlivenessProbeso Kubernetes gates traffic and restarts on the same signal. - Reference secrets via a
Secretobject or external secret store; never inline credentials in the manifest. - Drop all Linux capabilities and add back only what the workload requires (OWASP Docker Top 10 — see References).
Step 7: Scan the Image and Gate on Findings
- Build the image, then scan it with a vulnerability scanner (Trivy, Grype, or
docker scout) before pushing. Trivy also runs CIS Docker/Kubernetes benchmark checks (Trivy / CIS — see References). - Gate: block the push on any fixable HIGH or CRITICAL CVE. For an unfixable CVE with no upstream patch, record an explicit accepted-risk note with the CVE id and a re-check date rather than silently shipping.
- Re-scan after a base-image bump — a new base introduces a new CVE set.
- Optionally run a config linter (
hadolintfor the Dockerfile, Docker Bench for Security against the host) and surface findings alongside the CVE report.
Error Handling
- Final image is unexpectedly large: confirm the runtime stage copies only artifacts (Step 2), the base is a
-slim/distroless variant (Step 1), and.dockerignoreexcludesnode_modules/build output (Step 4). A multi-hundred-MB image usually means the build toolchain leaked into the runtime stage. - App fails to start as non-root: the process is binding a privileged port (<1024) or writing to a path it no longer owns. Bind a high port and map it at the orchestrator, or
chownthe writable path to the app UID in the build stage. Do not revert to root. readOnlyRootFilesystem: truebreaks the app: mount anemptyDirvolume at the specific writable path (cache, tmp) the app needs rather than disabling the read-only root.- Scanner reports a CVE with no fix: do not silently ship. Record the CVE id, affected package, and a re-evaluation date as an accepted-risk note; escalate if it is on the request path and exploitable.
- Build needs a secret: never
COPYorARGit into a layer. Use a BuildKit secret mount (RUN --mount=type=secret); if that is unavailable, build the artifact outside the image andCOPYonly the result.
Definition of Done
- [ ] Multi-stage Dockerfile: build stage and a runtime stage carrying only runtime artifacts
- [ ] Runs as a non-root user with an explicit UID; no
sudo;HEALTHCHECKpresent - [ ] Minimal pinned base image (no
latest);.dockerignoreverified to shrink the build context - [ ] docker-compose for local dev with per-service healthchecks and gitignored secret injection
- [ ] Kubernetes manifest with
securityContext(runAsNonRoot, dropped capabilities, read-only root), resource limits, and probes - [ ] Image scanned; build/push gated on fixable HIGH/CRITICAL CVEs; accepted-risk notes recorded for unfixable findings
- [ ] No secret in any image layer, compose file, or manifest
References
- Docker, Inc. "Building best practices — Dockerfile."
https://docs.docker.com/build/building/best-practices/(accessed 2026-06-02, Docker Docs, official-docs). Source for the minimal-base-image guidance (Step 1), the non-rootUSERwith explicit UID and the no-sudo/gosurule (Step 3), andCOPY-over-ADD. Multi-stage build mechanics (Step 2):https://docs.docker.com/build/building/multi-stage/. - OWASP Foundation. "Docker Security Cheat Sheet."
https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html(accessed 2026-06-02, OWASP, official-docs). Source for the drop-all-capabilities-then-add-back posture (Steps 6), the no-secrets-in-layers rule (Steps 2–4), and the root-escape blast-radius rationale behind the mandatory non-root user (Step 3). Container-environment controls: OWASP Docker Top 10https://owasp.org/www-project-docker-top-10/. - Center for Internet Security / Aqua Security (Trivy). "CIS Docker Benchmark — HEALTHCHECK (control 4.6) and Trivy CIS benchmark scanning."
https://www.cisecurity.org/benchmark/dockerandhttps://www.aquasec.com/blog/trivy-kubernetes-cis-benchmark-scanning/(accessed 2026-06-02, CIS official-docs + Aqua Security established-library). Source for theHEALTHCHECKrequirement (Step 3), the KubernetessecurityContexthardening fields (Step 6), and the Trivy CIS-benchmark image-scan gate (Step 7).
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: hatch3r
- Source: hatch3r/hatch3r
- License: MIT
- Homepage: https://docs.hatch3r.com
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.