Dockerfile Best Practices Reviewer

💻 PROGRAMMING · DevOps advanced ⭐ 82

Agent skill that reviews Dockerfiles for security, size and cache issues, then writes a corrected Dockerfile and .dockerignore.

Заполните поля:

Значения подставятся в промпт ниже.

---
name: dockerfile-best-practices-reviewer
description: Reviews Dockerfiles for security, image size, build cache, and runtime reliability: secrets baked into layers, running as root, unpinned base images, curl piped to a shell, ADD misuse, apt and pip caches left in layers, dependency installs that bust the cache, shell-form CMD, and missing multi-stage builds or health checks, then writes a corrected Dockerfile and .dockerignore. Use when a user shares a Dockerfile and asks "review my Dockerfile", "why is my image so big?", "is this container secure?", or "why does every build reinstall dependencies?". Includes a tested stdlib Python linter.
---

# Dockerfile Best Practices Reviewer

You help developers ship container images that are small, fast to rebuild, and safe to run. You read a Dockerfile the way a careful platform engineer would: you find what leaks secrets or gives away root, what makes the image heavy, and what forces slow rebuilds, and you return a corrected Dockerfile that still builds and runs the same app.

## Files in this skill

- `scripts/lint_dockerfile.py` - parses a Dockerfile (continuation lines, multi-stage builds) and reports HIGH, WARN, and INFO findings with a fix for each (Python 3 standard library only)
- `references/dockerfile-rules.md` - every rule the linter checks, why it matters, and the correct pattern
- `references/language-patterns.md` - proven multi-stage patterns for Node.js, Python, Go, and Java, plus a starter .dockerignore
- `templates/dockerfile-review.md` - the review report to return
- `examples/example-node-api-review.md` - a worked review of a Node.js API Dockerfile

## Workflow

### 1. Collect the facts
Ask, or assume and say so:
- The Dockerfile, and the .dockerignore if there is one.
- Language and framework, how the app is built and started, and the port.
- Where it runs (Kubernetes, ECS, a VM with Docker Compose, a laptop) and whether that platform already has health checks.
- Constraints: required base images (company registry, distroless, Alpine or Debian), native dependencies, and whether the build uses BuildKit.

### 2. Run the linter
```bash
python3 scripts/lint_dockerfile.py Dockerfile
python3 scripts/lint_dockerfile.py services/*/Dockerfile --fail-on warn # stricter, for CI
python3 scripts/lint_dockerfile.py - --json /tmp/install.sh" | sha256sum -c - \
 && sh /tmp/install.sh && rm /tmp/install.sh
```

### chmod-777
Detected: `chmod 777` or `chmod -R 777`.
Why: any user or process in the container can modify the files, including app code.
Use instead: `COPY --chown=app:app` and `chmod 755` for executables, `644` for files.

## WARN

| Rule | Detected | Why | Fix |
|---|---|---|---|
| unpinned-base | FROM without tag or with :latest | builds change silently; hard to reproduce or roll back | `FROM node:20.11-bookworm-slim` or pin a digest `@sha256:...` |
| use-copy | ADD for local files or URLs | ADD auto-extracts archives and downloads without checksums | COPY for files; curl with checksum or `ADD --checksum=` for URLs; ADD is fine for local tar archives you want extracted |
| update-alone | `apt-get update` (or apk/yum) without install in the same RUN | the cached update layer gets reused with a newer install line, so packages come from a stale index | `RUN apt-get update && apt-get install -y ...` |
| cache-bust | dependency install after `COPY . .` | any source change invalidates the install layer, so every build reinstalls everything | copy manifests first, install, then copy the source |
| sudo | sudo in RUN | not needed (build runs as root until USER), adds a setuid binary | run the step before USER |
| ssh-port | EXPOSE 22 | containers should not run SSH daemons | `docker exec`, `kubectl exec` |
| multiple-cmd | more than one CMD or ENTRYPOINT in a stage | only the last one counts; usually a mistake | keep one |

## INFO

| Rule | Fix |
|---|---|
| apt-recommends | `apt-get install -y --no-install-recommends ...` |
| apt-lists | end the same RUN with `&& rm -rf /var/lib/apt/lists/*` |
| apk-cache | `apk add --no-cache ...` |
| pip-cache | `pip install --no-cache-dir ...` or a BuildKit cache mount |
| npm-ci | `npm ci --omit=dev` uses the lockfile exactly |
| shell-form | `CMD ["node", "server.js"]` so the app is PID 1 and receives SIGTERM for graceful shutdown |
| cd-in-run | `WORKDIR /app` |
| no-healthcheck | `HEALTHCHECK CMD curl -fsS http://localhost:3000/health || exit 1` if the platform has no probe; Kubernetes ignores HEALTHCHECK and uses its own probes |
| single-stage | build in a builder stage; copy only the output into a slim runtime stage |
| many-layers | combine related RUN steps; fewer, purposeful layers |
| dockerignore | exclude `.git`, `node_modules`, `.env*`, build output, logs, and local secrets |

## What the linter cannot see
- Whether copied files contain secrets (check .dockerignore and the repo).
- Vulnerable packages in the base image or dependencies: suggest an image scanner in CI.
- Whether the non-root user can write where the app writes (logs, uploads, caches).
- Architecture issues (arm64 vs amd64), native modules, and Alpine musl compatibility.
FILE:references/language-patterns.md
# Multi-stage patterns by language

Adapt versions to the project. All examples run as a non-root user, use exec-form CMD, and keep build tools out of the runtime image.

## Node.js (npm)
```dockerfile
# syntax=docker/dockerfile:1
FROM node:20.11-bookworm-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

FROM node:20.11-bookworm-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20.11-bookworm-slim
ENV NODE_ENV=production
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build --chown=node:node /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]
```
Skip the build stage if there is no compile step. Use `node:-alpine` only if native modules work with musl.

## Python (pip)
```dockerfile
FROM python:3.12-slim AS build
WORKDIR /app
RUN python -m venv /venv
ENV PATH="/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

FROM python:3.12-slim
ENV PATH="/venv/bin:$PATH" PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
WORKDIR /app
COPY --from=build /venv /venv
RUN useradd -r -u 10001 app
COPY --chown=app:app . .
USER app
EXPOSE 8000
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]
```
If packages need compilers (psycopg2, numpy from source), install `build-essential` only in the build stage.

## Go
```dockerfile
FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/app ./cmd/app

FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/app /app
USER nonroot
ENTRYPOINT ["/app"]
```
A static Go binary on distroless or scratch gives images of a few MB to tens of MB.

## Java (Maven)
```dockerfile
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /src
COPY pom.xml .
RUN mvn -q dependency:go-offline
COPY src ./src
RUN mvn -q package -DskipTests

FROM eclipse-temurin:21-jre
RUN useradd -r -u 10001 app
WORKDIR /app
COPY --from=build --chown=app:app /src/target/app.jar app.jar
USER app
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-jar", "app.jar"]
```

## Starter .dockerignore
```
.git
.gitignore
.dockerignore
Dockerfile*
node_modules
dist
build
target
__pycache__
*.pyc
.venv
.env
.env.*
*.log
coverage
.idea
.vscode
secrets/
*.pem
*.key
```

## Measuring the result
- `docker build -t app:review .` then `docker image ls app:review` for size.
- `docker history app:review` to see which layers are large.
- Change one source file and rebuild: the dependency install step should say CACHED.
FILE:templates/dockerfile-review.md
# Dockerfile review: ${service_name}

**App:** ${language_and_framework}, started with ${start_command}, port ${port}
**Runs on:** ${platform}
**Linter:** HIGH ${high} / WARN ${warn} / INFO ${info} before; HIGH 0 / WARN ${warn_after} / INFO ${info_after} after

## Summary

Two or three sentences: the biggest security problem, the biggest size or speed problem, and the expected improvement.

## Findings

| # | Level | Line | Problem | Fix |
|---|---|---|---|---|
| 1 | HIGH | | | |

## Action needed outside the Dockerfile

- Rotate: (names of any secrets that were in the file or image, never their values)
- Platform: health probes, read-only filesystem, resource limits

## Corrected Dockerfile

```dockerfile
(full corrected Dockerfile with one-line comments on changed parts)
```

## .dockerignore

```
(entries)
```

## Build and verify

```bash
docker build -t ${image}:review .
docker run --rm -p ${port}:${port} ${image}:review
docker image ls ${image}:review
python3 scripts/lint_dockerfile.py Dockerfile
```

## Expected impact (estimate)

- Image size: from about ... to about ...
- Rebuild after a source change: dependency layer cached
- Security: non-root, no secrets in layers, pinned base
FILE:examples/example-node-api-review.md
# Example: review of a Node.js API Dockerfile

## User request

"Our orders API image is 1.2 GB and every build reinstalls all npm packages, even when I change one line. Can you review the Dockerfile? It runs on Kubernetes, Express app, `node server.js`, port 3000. No native modules as far as I know."

## Input Dockerfile

```dockerfile
FROM node
ENV NODE_ENV=production
ENV DB_PASSWORD=supersecret123
RUN apt-get update
RUN apt-get install -y curl git build-essential
WORKDIR /app
COPY . .
RUN npm install
RUN curl -fsSL https://example.com/install-agent.sh | bash
RUN chmod -R 777 /app
ADD config.json /app/config.json
EXPOSE 3000 22
CMD node server.js
```

## Linter run

```
$ python3 scripts/lint_dockerfile.py Dockerfile
Dockerfile: 1 stage(s): node
 HIGH 4 WARN 5 INFO 7
 HIGH line 1 root-user the final image runs as root
 HIGH line 3 secret-in-env ENV DB_PASSWORD contains a literal secret; it is stored in the image layers and history
 HIGH line 9 curl-pipe-shell downloads a script and pipes it straight into a shell
 HIGH line 10 chmod-777 chmod 777 makes files writable by every user
 WARN line 1 unpinned-base base image 'node' has no tag, so it means :latest and changes without notice
 WARN line 4 update-alone 'apt-get update' runs in its own layer; a later install may use a stale cached index
 WARN line 8 cache-bust dependencies are installed after 'COPY . .' (line 7), so any source change reinstalls them
 WARN line 11 use-copy ADD is used for local files; it also auto-extracts archives and fetches URLs
 WARN line 12 ssh-port port 22 (SSH) is exposed; containers should not run an SSH server
 INFO line 1 no-healthcheck no HEALTHCHECK in the final stage
 INFO line 1 single-stage build tools are installed in the only stage, so they ship in the final image
 INFO line 5 apt-recommends apt installs recommended packages too
 INFO line 5 apt-lists apt package lists are left in the layer (tens of MB)
 INFO line 7 dockerignore COPY of the whole build context
 INFO line 8 npm-ci npm install can change the lockfile and versions
 INFO line 13 shell-form shell-form CMD runs under /bin/sh -c, so the app is not PID 1 and may miss SIGTERM
```

## Review (filled template, shortened)

**Linter:** HIGH 4 / WARN 5 / INFO 7 before; HIGH 0 / WARN 0 / INFO 0 after

**Summary:** The image ships a database password in its layers and runs as root, and it pipes a remote script into bash. It is large because it uses the full `node` image with compilers and git, and slow because `COPY . .` comes before `npm install`. The corrected two-stage Dockerfile removes the secret, runs as the built-in `node` user, and caches dependencies; expect roughly 200 to 300 MB instead of 1.2 GB (measure with `docker image ls`).

| # | Level | Line | Problem | Fix |
|---|---|---|---|---|
| 1 | HIGH | 3 | DB_PASSWORD literal in ENV, visible in `docker history` | removed; inject from a Kubernetes Secret at runtime |
| 2 | HIGH | - | runs as root | `USER node` (built into the official image) |
| 3 | HIGH | 9 | remote install script piped into bash | removed: the agent now runs as a Kubernetes sidecar; if it must stay, download, verify the checksum, then run |
| 4 | HIGH | 10 | chmod -R 777 /app | `COPY --chown=node:node` instead |
| 5 | WARN | 1 | untagged `node` base | `node:20.11-bookworm-slim` |
| 6 | WARN | 4 | apt-get update in its own layer | combined with install and list cleanup |
| 7 | WARN | 8 | npm install after `COPY . .` | manifests first, `npm ci --omit=dev` in a deps stage |
| 8 | WARN | 11 | ADD for a local file | COPY |
| 9 | WARN | 12 | EXPOSE 22 | removed; use `kubectl exec` |
| 10 | INFO | 5, 13 | build tools and git in the runtime image, shell-form CMD | dropped (no native modules), exec-form CMD |

**Action needed outside the Dockerfile:** rotate DB_PASSWORD now (it is in every image pushed so far), and add it to the Deployment from a Kubernetes Secret. Kubernetes ignores HEALTHCHECK, so also add a readiness and liveness probe on /health; the HEALTHCHECK below helps local Docker and Compose.

## Corrected Dockerfile

```dockerfile
# syntax=docker/dockerfile:1
FROM node:20.11-bookworm-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

FROM node:20.11-bookworm-slim
ENV NODE_ENV=production
WORKDIR /app
RUN apt-get update \
 && apt-get install -y --no-install-recommends curl \
 && rm -rf /var/lib/apt/lists/*
COPY --from=deps /app/node_modules ./node_modules
COPY --chown=node:node src/ ./src/
COPY --chown=node:node config.json ./
USER node
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s CMD curl -fsS http://localhost:3000/health || exit 1
CMD ["node", "src/server.js"]
```

## .dockerignore

```
.git
node_modules
.env
.env.*
*.log
coverage
Dockerfile*
```

## Verify

```
$ python3 scripts/lint_dockerfile.py Dockerfile
Dockerfile: 2 stage(s): node:20.11-bookworm-slim AS deps, node:20.11-bookworm-slim
 HIGH 0 WARN 0 INFO 0
 no problems found
```

Note: the source now lives in `src/`, so the start command became `node src/server.js`; if your entry file is elsewhere, keep your original path. If a dependency turns out to need compilers, install `build-essential` and `python3` in the deps stage only.
FILE:scripts/lint_dockerfile.py
#!/usr/bin/env python3
"""Review a Dockerfile for security, image size, build cache, and reliability problems.

Usage:
 python3 lint_dockerfile.py Dockerfile [more Dockerfiles] [--json] [--fail-on high|warn|info]
 python3 lint_dockerfile.py - 1 else ""))
 buf, start = "", None
 if buf:
 parts = buf.split(None, 1)
 out.append((start, parts[0].upper(), parts[1] if len(parts) > 1 else ""))
 return out

def review(text):
 lines = logical_lines(text)
 findings = []

 def add(level, rule, line, msg, fix):
 findings.append({"level": level, "rule": rule, "line": line, "message": msg, "fix": fix})

 if not any(ins == "FROM" for _, ins, _ in lines):
 raise ValueError("no FROM instruction found (is this a Dockerfile?)")
 stages, cur = [], None
 for ln, ins, args in lines:
 if ins == "FROM":
 m = re.match(r"(?:--platform=\S+\s+)?(\S+)(?:\s+AS\s+(\S+))?", args, re.I)
 cur = {"line": ln, "image": m.group(1) if m else args, "name": m.group(2) if m else None, "ins": []}
 stages.append(cur)
 elif cur is not None:
 cur["ins"].append((ln, ins, args))
 names = {s["name"].lower() for s in stages if s["name"]}
 arg_names = {a.split("=")[0].strip() for _, i, a in lines if i == "ARG"}

 for s in stages:
 img = s["image"]
 if img.lower() in names or img == "scratch" or img.startswith("$"):
 pass
 elif "@sha256:" in img:
 pass
 elif ":" not in img.split("/")[-1]:
 add("warn", "unpinned-base", s["line"], f"base image '{img}' has no tag, so it means :latest and changes without notice",
 f"pin a version, e.g. {img}:-slim, or a digest")
 elif img.endswith(":latest"):
 add("warn", "unpinned-base", s["line"], f"base image '{img}' uses :latest", "pin a specific version tag or digest")

 final = stages[-1]
 run_count = 0
 for idx, s in enumerate(stages):
 cmds = [x for x in s["ins"] if x[1] in ("CMD", "ENTRYPOINT")]
 for kind in ("CMD", "ENTRYPOINT"):
 k = [x for x in cmds if x[1] == kind]
 if len(k) > 1:
 add("warn", "multiple-cmd", k[-1][0], f"{len(k)} {kind} instructions in one stage; only the last one counts",
 f"keep a single {kind}")
 copied_all = None
 for ln, ins, args in s["ins"]:
 low = args.lower()
 if ins in ("ENV", "ARG"):
 pairs = re.findall(r"([A-Za-z_][A-Za-z0-9_]*)(?:(?:=|\s+)(\"[^\"]*\"|'[^']*'|[^\s=]\S*))?", args) if ins == "ARG" or "=" in args else [tuple((args.split(None, 1) + [""])[:2])]
 for key, val in pairs:
 if SECRET_KEY.search(key) and val and not val.startswith("$") and val.strip("\"'"):
 add("high", "secret-in-env", ln, f"{ins} {key} contains a literal secret; it is stored in the image layers and history",
 "pass secrets at build time with RUN --mount=type=secret, or at run time via environment or a secrets manager")
 if ins == "ARG" and any(SECRET_KEY.search(k) for k, _ in pairs) and not any(v for k, v in pairs if SECRET_KEY.search(k)):
 add("warn", "secret-in-env", ln, f"ARG {pairs[0][0]} looks like a secret; build args are visible in image history",
 "use RUN --mount=type=secret instead of a build arg")
 if ins == "RUN":
 run_count += 1
 if re.search(r"(curl|wget)\b[^|;&]*\|\s*(sudo\s+)?(ba|z|da)?sh\b", low):
 add("high", "curl-pipe-shell", ln, "downloads a script and pipes it straight into a shell",
 "download to a file, verify a checksum or signature, then run it")
 if re.search(r"chmod\s+(-r\s+)?0?777\b", low):
 add("high", "chmod-777", ln, "chmod 777 makes files writable by every user", "grant only the permissions needed, e.g. chmod 755 or chown to the app user")
 if re.search(r"(--password[= ]\S+|://[^/\s:@]+:[^@\s$]+@)", args) or re.search(r"\b(token|api[_-]?key)=\w{8,}", low):
 add("high", "secret-in-run", ln, "a password, token, or credentials URL appears in a RUN command and stays in the image history",
 "use RUN --mount=type=secret or fetch credentials at run time")
 if re.search(r"\bsudo\b", low):
 add("warn", "sudo", ln, "sudo inside a Dockerfile is unnecessary and widens the attack surface", "run the step before switching USER, without sudo")
 for upd, inst in (("apt-get update", "apt-get install"), ("apt update", "apt install"), ("apk update", "apk add"), ("yum update", "yum install")):
 if upd in low and inst not in low:
 add("warn", "update-alone", ln, f"'{upd}' runs in its own layer; a later install may use a stale cached index",
 f"combine: RUN {upd} && {inst} ... in one RUN")
 if "apt-get install" in low or "apt install" in low:
 if "--no-install-recommends" not in low:
 add("info", "apt-recommends", ln, "apt installs recommended packages too", "add --no-install-recommends")
 if "/var/lib/apt/lists" not in low:
 add("info", "apt-lists", ln, "apt package lists are left in the layer (tens of MB)", "end the same RUN with && rm -rf /var/lib/apt/lists/*")
 if "apk add" in low and "--no-cache" not in low:
 add("info", "apk-cache", ln, "apk cache is kept in the layer", "use apk add --no-cache")
 if re.search(r"\bpip3?\s+install\b", low) and "--no-cache-dir" not in low and "--mount=type=cache" not in low:
 add("info", "pip-cache", ln, "pip keeps its download cache in the layer", "add --no-cache-dir (or use a cache mount)")
 if any(re.fullmatch(r"\s*npm\s+(install|i)(\s+--?(?!g\b|global\b)[\w-]+(=\S+)?)*\s*", seg) for seg in re.split(r"&&|;|\|\|", low)):
 add("info", "npm-ci", ln, "npm install can change the lockfile and versions", "use npm ci (with --omit=dev for production)")
 if re.match(r"cd\s+\S+\s*&&", low) or re.search(r"&&\s*cd\s+/", low):
 add("info", "cd-in-run", ln, "cd in RUN only affects that one command", "use WORKDIR /path")
 if copied_all and re.search(r"\b(npm (ci|install)|pip3? install -r|poetry install|bundle install|go mod download|composer install|yarn install)\b", low):
 add("warn", "cache-bust", ln, f"dependencies are installed after 'COPY {copied_all[1]}' (line {copied_all[0]}), so any source change reinstalls them",
 "copy only the manifest and lockfile first (e.g. COPY package*.json ./), install, then COPY the rest")
 copied_all = None
 if ins == "ADD":
 src = args.split()[0] if args.split() else ""
 if re.match(r"https?://", src):
 add("warn", "use-copy", ln, "ADD with a URL downloads without checksum verification and leaves the file in a layer",
 "use RUN curl -fsSL ... with a checksum check, or ADD --checksum=sha256:...")
 elif not re.search(r"\.(tar|tar\.gz|tgz|tar\.xz|tar\.bz2)$", src):
 add("warn", "use-copy", ln, "ADD is used for local files; it also auto-extracts archives and fetches URLs", "use COPY for plain files")
 if ins == "COPY" and not args.startswith("--from"):
 parts = [p for p in args.split() if not p.startswith("--")]
 if parts and parts[0] in (".", "./", "*"):
 copied_all = (ln, " ".join(parts))
 add("info", "dockerignore", ln, "COPY of the whole build context", "make sure .dockerignore excludes .git, node_modules, .env, build output, and secrets")
 if ins == "EXPOSE" and re.search(r"\b22\b", args):
 add("warn", "ssh-port", ln, "port 22 (SSH) is exposed; containers should not run an SSH server", "use docker exec / kubectl exec for access")
 if ins in ("CMD", "ENTRYPOINT") and not args.strip().startswith("["):
 add("info", "shell-form", ln, f"shell-form {ins} runs under /bin/sh -c, so the app is not PID 1 and may miss SIGTERM",
 f'use exec form: {ins} ["executable", "arg"]')

 users = [(ln, a.strip()) for ln, i, a in final["ins"] if i == "USER"]
 if not users or users[-1][1].split(":")[0] in ("root", "0"):
 where = users[-1][0] if users else final["line"]
 add("high", "root-user", where, "the final image runs as root",
 "create an unprivileged user (e.g. RUN useradd -r -u 10001 app) and add USER app before CMD")
 if not any(i == "HEALTHCHECK" for _, i, _ in final["ins"]):
 add("info", "no-healthcheck", final["line"], "no HEALTHCHECK in the final stage", "add HEALTHCHECK CMD ... if no orchestrator health probe is configured")
 if len(stages) == 1 and any(BUILD_TOOLS.search(a) for _, i, a in final["ins"] if i == "RUN"):
 add("info", "single-stage", final["line"], "build tools are installed in the only stage, so they ship in the final image",
 "use a multi-stage build: compile in a builder stage, COPY --from=builder only the output")
 if run_count > 10:
 add("info", "many-layers", final["line"], f"{run_count} RUN instructions; related steps can be combined", "group related commands with && in fewer RUN steps")
 return {"stages": [{"line": s["line"], "image": s["image"], "name": s["name"]} for s in stages], "findings": findings}

def main(argv):
 as_json, fail_on, files = False, "high", []
 it = iter(argv)
 for a in it:
 if a == "--json":
 as_json = True
 elif a == "--fail-on":
 fail_on = next(it, "").lower()
 if fail_on not in LEVELS:
 print("error: --fail-on must be high, warn, or info", file=sys.stderr)
 return 2
 elif a in ("-h", "--help"):
 print(__doc__)
 return 0
 elif a.startswith("--"):
 print(f"error: unknown option {a}", file=sys.stderr)
 return 2
 else:
 files.append(a)
 if not files:
 print("error: give a Dockerfile path, or - for stdin", file=sys.stderr)
 return 2
 reports, worst = [], -1
 for f in files:
 try:
 text = sys.stdin.read() if f == "-" else open(f, encoding="utf-8").read()
 r = review(text)
 except (OSError, UnicodeDecodeError, ValueError) as e:
 print(f"error: {f}: {e}", file=sys.stderr)
 return 2
 r["file"] = f
 r["findings"].sort(key=lambda x: (-LEVELS[x["level"]], x["line"]))
 for x in r["findings"]:
 worst = max(worst, LEVELS[x["level"]])
 reports.append(r)
 if as_json:
 print(json.dumps(reports, indent=2))
 else:
 for r in reports:
 c = {lv: sum(1 for x in r["findings"] if x["level"] == lv) for lv in LEVELS}
 st = ", ".join(f"{s['image']}" + (f" AS {s['name']}" if s["name"] else "") for s in r["stages"])
 print(f"{r['file']}: {len(r['stages'])} stage(s): {st}")
 print(f" HIGH {c['high']} WARN {c['warn']} INFO {c['info']}")
 for x in r["findings"]:
 print(f" {x['level'].upper():= LEVELS[fail_on] else 0

if __name__ == "__main__":
 sys.exit(main(sys.argv[1:]))
#docker#dockerfile#security#review
🎁 Забери ещё 6 лучших промптов бесплатно
Отборная подборка + доступ к библиотеке 2000+ промптов. Без оплаты.

Кураторская подборка fizoni.com · структура, примеры использования и рабочие сценарии

Похожие промпты

💡 Понравился этот промпт?

📦 Prompts for Programmers — 456 промптов

Вместо одного промпта — целый набор по теме. Готовые процессы, структура, быстрый старт. Код, рефакторинг, дебаг, архитектура, ревью, SQL и API.…

Забрать набор — $2.99 🎁 6 бесплатных промптов

Всего $2.99 · мгновенный доступ · оплата картой

🎁 Забери 6 лучших промптов бесплатно

Понравился этот промпт? Мы собрали ещё 6 отборных — для бизнеса, кода и продуктивности. Плюс доступ к полной библиотеке 2000+ промптов.