# AI AutoFix (ACP) Source: https://tally.wharflab.com/guides/ai-autofix Use ACP-compatible coding agents to fix complex Dockerfile issues that are too risky for deterministic rewrites. tally supports **opt-in AI AutoFix** for the kinds of Dockerfile improvements that are hard to express as a purely mechanical rewrite — or too risky to apply without extra validation. Instead of asking you for an API key, tally integrates with [**ACP (Agent Client Protocol)**](https://agentclientprotocol.com/get-started/introduction) — a protocol created by the [Zed editor](https://zed.dev/) to standardize how tools talk to coding agents. This means: * You choose **which agent** you want to use (Gemini CLI, OpenCode, GitHub Copilot CLI, and more). * You keep **credentials and model choice** inside that agent. * tally stays a **linter first** — fast and deterministic — and uses AI only when you explicitly opt in. ## How it works tally treats AI AutoFix as a normal part of its existing fix pipeline: 1. A rule detects a violation and attaches a **SuggestedFix** marked as async. 2. tally builds a **prompt** containing the Dockerfile text and structured rule evidence. 3. tally runs your configured agent via **ACP over stdio**. 4. The agent returns a **unified diff patch** targeting the exact Dockerfile bytes from the prompt. 5. tally **validates** the patch: parses it, re-lints the result, and checks invariants. 6. If valid, the patch is applied. If not, tally skips the fix and continues linting. Linting always works even when AI is misconfigured or unavailable. ## Quick start Choose an ACP-capable CLI agent. Any of these work out of the box: * [Gemini CLI](https://agentclientprotocol.com/agents/gemini-cli) (native ACP) * [OpenCode](https://opencode.ai/docs/acp/) (native ACP) * [GitHub Copilot CLI](https://docs.github.com/en/copilot/reference/acp-server) (native ACP) * [Cline CLI v2](https://docs.cline.bot/cline-cli/acp-editor-integrations) (native ACP) * [Kiro CLI](https://kiro.dev/docs/cli/acp/) (native ACP) * [Docker agent](https://docker.github.io/docker-agent/features/acp/) (native ACP) Browse the full registry at [agentclientprotocol.com/get-started/registry](https://agentclientprotocol.com/get-started/registry). Create or update your `.tally.toml`. The example below uses Gemini CLI with MCP servers disabled for lower latency: ```toml theme={null} [ai] enabled = true timeout = "90s" max-input-bytes = 262144 redact-secrets = true command = [ "gemini", "--experimental-acp", "--allowed-mcp-server-names=none", "--model=gemini-3-flash-preview", ] ``` `--allowed-mcp-server-names` is an allowlist. Passing a name you don't have configured (like `none`) effectively disables all MCP servers. tally doesn't provide any MCP servers to the agent today, so enabling MCP is usually just extra startup and latency overhead. AI fixes are intentionally marked **unsafe** and require both `--fix` and `--fix-unsafe`. For best results, narrow the scope to a single rule: ```bash theme={null} tally lint \ --fix --fix-unsafe \ --fix-rule tally/prefer-multi-stage-build \ path/to/Dockerfile ``` To prevent AI fixes from running accidentally, set the rule's fix mode to `"explicit"` in your config: ```toml theme={null} [rules.tally.prefer-multi-stage-build] fix = "explicit" ``` ## Recommended setup (low latency) Dockerfiles are a mature domain that most modern models understand well. For AI fixes, you usually don't need external tools or context servers — you want fast, predictable transformations. **Recommended:** * A fast or smaller model with solid general reasoning. * Disable agent-side tool integrations (MCP servers) unless you know you need them. ```bash theme={null} # Gemini CLI — fast model, no MCP overhead gemini --experimental-acp --allowed-mcp-server-names=none --model=gemini-3-flash-preview ``` ## Configuration reference ### Config file (`.tally.toml`) All AI settings live under `[ai]`: ```toml theme={null} [ai] enabled = false # Default: false command = ["gemini", "--experimental-acp", "--allowed-mcp-server-names=none", "--model=gemini-3-flash-preview"] timeout = "90s" # Per-fix timeout max-input-bytes = 262144 # Prompt size limit (bytes) redact-secrets = true # Redact obvious secrets (default: true) ``` | Setting | Default | Description | | -------------------- | --------- | ---------------------------------------------------- | | `ai.enabled` | `false` | Master kill-switch for AI features | | `ai.command` | *(empty)* | ACP agent argv (stdio). If empty, AI fixes can't run | | `ai.timeout` | `"90s"` | Per-fix timeout for the ACP interaction | | `ai.max-input-bytes` | `262144` | Maximum prompt size to send to the agent | | `ai.redact-secrets` | `true` | Redact obvious secrets in prompts (best-effort) | ### Environment variables ```bash theme={null} TALLY_AI_ENABLED=true TALLY_ACP_COMMAND="gemini --experimental-acp --allowed-mcp-server-names=none --model=gemini-3-flash-preview" TALLY_AI_TIMEOUT=90s TALLY_AI_MAX_INPUT_BYTES=262144 TALLY_AI_REDACT_SECRETS=true ``` ### CLI flags ```bash theme={null} --ai # Enable AI (when ai.command is already in .tally.toml) --acp-command "..." # Set the ACP agent command line (also enables AI) --ai-timeout 90s # Override ai.timeout --ai-max-input-bytes 262144 # Override ai.max-input-bytes --ai-redact-secrets=false # Override ai.redact-secrets ``` If your agent command needs complex quoting, prefer `ai.command = ["arg1", "arg2", ...]` in `.tally.toml` rather than `--acp-command`. ## Supported ACP agents ### Native ACP agents | Agent | Link | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | | Gemini CLI | [agentclientprotocol.com/agents/gemini-cli](https://agentclientprotocol.com/agents/gemini-cli) | | OpenCode | [opencode.ai/docs/acp/](https://opencode.ai/docs/acp/) | | GitHub Copilot CLI | [docs.github.com/en/copilot/reference/acp-server](https://docs.github.com/en/copilot/reference/acp-server) | | Kiro CLI | [kiro.dev/docs/cli/acp/](https://kiro.dev/docs/cli/acp/) | | Cline CLI v2 | [docs.cline.bot/cline-cli/acp-editor-integrations](https://docs.cline.bot/cline-cli/acp-editor-integrations) | | Docker agent | [docker.github.io/docker-agent/features/acp/](https://docker.github.io/docker-agent/features/acp/) | | QwenCode | [qwenlm.github.io/qwen-code-docs](https://qwenlm.github.io/qwen-code-docs/en/users/integration-zed/#install-from-acp-registry-recommend) | ### Zed-maintained adapters | Agent | Adapter | | ---------------- | ------------------------------------------------------------------------------------------------ | | Claude Code | [agentclientprotocol.com/agents/claude-code](https://agentclientprotocol.com/agents/claude-code) | | OpenAI Codex CLI | [agentclientprotocol.com/agents/codex](https://agentclientprotocol.com/agents/codex) | ## Rules with AI AutoFix Today tally routes AI AutoFix to two rule objectives. Each objective owns its own prompt, validators, and acceptance criteria: | Rule | Objective | What it does | | ------------------------------------------------------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------- | | [`tally/prefer-multi-stage-build`](/rules/tally/prefer-multi-stage-build) | `prefer-multi-stage-build` | Converts single-stage Dockerfiles into a builder + runtime split, preserving final-stage runtime invariants. | | [`tally/gpu/prefer-uv-over-conda`](/rules/tally/gpu/prefer-uv-over-conda) | `prefer-uv-over-conda` | Migrates narrow GPU Python Dockerfiles from conda/mamba/micromamba to uv, preserving final-stage runtime invariants. | Each rule emits a whole-file rewrite, so only one AI fix can land per `--fix` invocation. If a single Dockerfile triggers both rules, run `--fix --fix-unsafe --fix-rule ` twice, once per rule, to compose the rewrites. ## Security and privacy ACP is a protocol, **not a sandbox**. If you run a local agent process that can access your machine, it can still do so outside of ACP. Treat the agent like any other executable you run locally. tally adds multiple guardrails for AI fixes: * **Explicit opt-in** — AI is off unless you set `ai.enabled = true`. * **Unsafe gating** — AI fixes require `--fix-unsafe` in addition to `--fix`. * **Minimal capabilities** — tally advertises no filesystem and no terminal capabilities via ACP. * **Secret redaction** — prompts are best-effort redacted before being sent to the agent (controlled by `ai.redact-secrets`). * **Strict output contract** — the agent must return a small, targeted diff patch that applies cleanly to the exact Dockerfile bytes tally sent. * **Validation loop** — tally re-parses, re-lints, and checks runtime invariants before accepting any proposed change. ## Troubleshooting: "Skipped N fixes" Common reasons a fix is skipped: | Reason | Fix | | ----------------------------------------------- | ------------------------------------------------------------------------------ | | `--fix` not passed | Add `--fix` to your command | | `--fix-unsafe` not passed | AI fixes always require `--fix-unsafe` | | `--fix-rule` set, but the rule didn't trigger | The rule had no violations for this Dockerfile | | `tally/prefer-multi-stage-build` not triggering | This rule only fires for Dockerfiles with exactly **one `FROM`** | | Agent timed out | Increase `--ai-timeout` or check stderr for the error message | | Agent failed | tally prints the reason on stderr and keeps stdout clean for JSON/SARIF output | ## Why ACP instead of API keys Many tools bolt AI onto a linter by asking for an OpenAI or Anthropic API key. That approach comes with trade-offs: * **Provider lock-in** — the linter becomes a mini "AI platform" that must track models, pricing, retries, and auth. * **Secret sprawl** — API keys end up in dotfiles, CI secrets, and team docs. * **Enterprise friction** — organizations often standardize on a specific gateway, proxy, or provider policy. * **Inconsistent experience** — your editor agent knows your preferences, but your linter uses a completely different stack. ACP inverts this: tally stays agent-agnostic, you bring your own agent and existing auth setup, and you can switch models or providers without waiting for tally to add a new integration. # Auto-fix Source: https://tally.wharflab.com/guides/auto-fix Apply safe and unsafe auto-fixes to Dockerfiles using tally lint --fix. tally can apply fixes automatically. Fixes are designed to be: * **Atomic** — a fix applies fully or not at all. * **Conflict-aware** — overlapping edits are skipped rather than producing a corrupted file. * **Configurable** — per-rule fix modes let you control exactly which fixes run and when. ## Basic usage Apply all safe fixes: ```bash theme={null} tally lint --fix Dockerfile ``` Apply unsafe fixes too (includes AI-powered fixes when enabled): ```bash theme={null} tally lint --fix --fix-unsafe Dockerfile ``` `--fix` is not supported when the lint entrypoint is a Bake or Compose file. Lint the Dockerfile directly when you want tally to modify files. Limit fixes to specific rules: ```bash theme={null} tally lint --fix --fix-unsafe \ --fix-rule hadolint/DL3008 \ --fix-rule tally/prefer-copy-heredoc \ Dockerfile ``` Use `--fix-rule` to limit the blast radius when applying fixes for the first time. Start with one rule at a time to review changes before committing. ## Safe vs. unsafe fixes | Mode | Flag | Description | | ------ | ------------------------ | -------------------------------------------------------------------------------- | | Safe | `--fix` | Applies mechanical rewrites with high confidence (🔧 in the rules reference) | | Unsafe | `--fix` + `--fix-unsafe` | Also applies riskier fixes that may change semantics, including AI-powered fixes | The 🔧 marker in the rules reference indicates a rule has auto-fix support. Safe fixes are things like adding a missing newline, reordering instructions, or converting `RUN echo` to a `COPY` heredoc. Unsafe fixes may restructure stages or change command arguments. ## Per-rule fix modes Control when fixes are allowed per rule in `.tally.toml`: ```toml theme={null} [rules.tally.prefer-copy-heredoc] fix = "always" # Apply whenever --fix is set (default) [rules.tally.prefer-multi-stage-build] fix = "explicit" # Only when --fix-rule includes this rule explicitly [rules.tally.no-trailing-spaces] fix = "unsafe-only" # Only when --fix-unsafe is also set [rules.tally.eol-last] fix = "never" # Never auto-fix this rule ``` Valid fix mode values: | Value | Behavior | | ------------- | ---------------------------------------------------------- | | `always` | Apply whenever `--fix` is passed (default) | | `never` | Never auto-fix, even when `--fix` is set | | `explicit` | Only when `--fix-rule ` is explicitly specified | | `unsafe-only` | Only when `--fix-unsafe` is also set | ## How conflict resolution works When two fixes would modify overlapping lines, tally skips the conflicting fix rather than applying a partial or corrupted change. The skipped fix is reported to stderr: ```text theme={null} Skipped 1 fixes note: skipped fix tally/prefer-copy-heredoc (Dockerfile): overlapping edit at line 14 ``` Fix skips are informational — linting continues and the violation is still reported so you can address it manually. ## Examples of fixable rules Rules marked 🔧 in the rules reference support auto-fix. Some notable examples: | Rule | Fix type | What it does | | ------------------------------------ | ----------- | --------------------------------------------------------- | | `tally/prefer-copy-heredoc` | Safe | Converts `RUN echo`/`cat`/`printf` to `COPY` heredoc | | `tally/prefer-copy-chmod` | Safe | Converts `COPY` + `RUN chmod` to `COPY --chmod` | | `tally/no-trailing-spaces` | Safe | Removes trailing whitespace | | `tally/eol-last` | Safe | Adds missing newline at end of file | | `tally/sort-packages` | Safe | Sorts package lists alphabetically | | `tally/epilogue-order` | Safe | Reorders `STOPSIGNAL`, `HEALTHCHECK`, `ENTRYPOINT`, `CMD` | | `tally/curl-should-follow-redirects` | Safe | Adds `-L` to `curl` commands | | `tally/prefer-multi-stage-build` | Unsafe (AI) | Converts single-stage builds to multi-stage | | `tally/prefer-package-cache-mounts` | Unsafe (AI) | Adds BuildKit cache mounts for package installs | ## AI AutoFix Some fixes are too complex to implement deterministically. For those, tally supports an opt-in AI resolver via ACP (Agent Client Protocol). AI fixes are marked unsafe and require both `--fix` and `--fix-unsafe`. See the [AI AutoFix (ACP)](/guides/ai-autofix) guide for setup instructions. # Build invocations Source: https://tally.wharflab.com/guides/build-invocations Lint Dockerfiles through Docker Buildx Bake and Docker Compose entrypoints. A build invocation is one planned build of one Dockerfile. When you lint a Bake or Compose file, tally resolves each target or service into the Dockerfile, build context, build args, platform, target stage, named contexts, and runtime metadata declared by that orchestrator. Use invocation-aware linting when the Dockerfile is not the whole source of truth for the build. Lint the targets from `docker-bake.hcl` or `docker-bake.json`. Lint services with a `build:` section from `compose.yaml`. Use the orchestrator's context instead of repeating `--context`. Attach Bake or Compose context to Dockerfile diagnostics in LSP clients. ## Supported entrypoints `tally lint` still uses one command. The behavior depends on the explicit path you pass. | Input | Behavior | | ----------------------------- | --------------------------------------------------------------------------------------------- | | `tally lint .` | Recursively discovers Dockerfiles and Containerfiles. No orchestrator discovery is performed. | | `tally lint Dockerfile` | Lints one Dockerfile directly. | | `tally lint docker-bake.hcl` | Resolves Bake targets and lints each resulting build invocation. | | `tally lint docker-bake.json` | Resolves Bake JSON targets and lints each resulting build invocation. | | `tally lint compose.yaml` | Resolves Compose services with `build:` sections and lints each resulting build invocation. | Orchestrator mode requires one explicit regular file. Do not mix a Bake or Compose file with other lint inputs in the same command. Directory and glob inputs keep their Dockerfile discovery behavior. This means `tally lint .` does not search for `compose.yaml` or `docker-bake.hcl`; pass those files explicitly when they are the build source of truth. ## Dockerfile mode and `--context` For direct Dockerfile linting, use `--context` when rules need access to files in the build context: ```bash theme={null} tally lint --context . Dockerfile ``` This is useful for checks that depend on `.dockerignore` or on files copied into the image. When you lint a Bake or Compose entrypoint, do not pass `--context`. tally derives the primary build context from the selected target or service. ```bash theme={null} # Correct: context comes from docker-bake.hcl tally lint docker-bake.hcl # Error: --context is only for direct Dockerfile mode tally lint --context . docker-bake.hcl ``` ## Docker Buildx Bake Lint all targets in the Bake default group: ```bash theme={null} tally lint docker-bake.hcl ``` Select specific targets or groups with `--target`: ```bash theme={null} tally lint docker-bake.hcl --target api --target worker ``` Example Bake file: ```hcl docker-bake.hcl theme={null} group "default" { targets = ["api", "worker"] } target "api" { context = "./services/api" dockerfile = "Dockerfile" target = "runtime" platforms = ["linux/amd64"] args = { NODE_VERSION = "22" } contexts = { base = "target:base" } } target "worker" { context = "./services/worker" dockerfile = "Containerfile" } ``` tally uses the resolved target data when linting: * `context` becomes the primary build context. * `dockerfile` selects the file to lint. * `args` with concrete values are available to Dockerfile analysis. * `target` selects the effective final stage. * `platforms` are available to platform-aware checks. * `contexts` are preserved as named build contexts. ### Bake limitations | Feature | Behavior | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `dockerfile-inline` | Error. tally needs a real Dockerfile path for diagnostics and fixes. | | Multi-file Bake setups | Error when sibling Bake files are detected. Pass one explicit file that contains the full target definition, or lint the resolved Dockerfiles directly. | | Remote Bake definition URLs | Not supported as lint entrypoints. Pass a local Bake file. | | Non-local primary contexts | Supported as invocation metadata, but tally cannot inspect remote files or resolve a relative Dockerfile from a remote context. | ## Docker Compose Lint all active services with a `build:` section: ```bash theme={null} tally lint compose.yaml ``` Select specific services with `--service`: ```bash theme={null} tally lint compose.yaml --service api --service worker ``` Example Compose file: ```yaml compose.yaml theme={null} services: api: build: context: ./services/api dockerfile: Dockerfile target: runtime args: NODE_VERSION: "22" additional_contexts: base: service:base ports: - "8080:8080" environment: NODE_ENV: production worker: build: context: ./services/worker dockerfile: Containerfile redis: image: redis:7 ``` In this example, `api` and `worker` produce lint invocations. `redis` does not, because it has no `build:` section. tally preserves both build-time and selected runtime metadata: * `build.context`, `build.dockerfile`, `build.args`, `build.platforms`, `build.target`, and `build.additional_contexts` * service `platform` when `build.platforms` is not set * service `environment`, `ports`, `expose`, `labels`, `networks`, `secrets`, `healthcheck`, `entrypoint`, `command`, `user`, `working_dir`, and `stop_signal` Some rules do not use all of this metadata yet. The data is still part of the invocation so current and future checks agree on the same planned build. ### Compose limitations | Feature | Behavior | | --------------------------------------- | ------------------------------------------------------------------------ | | `build.dockerfile_inline` | Error. tally needs a real Dockerfile path. | | Profile-gated build services | Error. tally does not guess which non-default profiles should be active. | | Selected service without `build:` | Error. `--service` must name a buildable service. | | Image-only services | Allowed, but they do not produce lint invocations. | | Unsaved Compose file edits in an editor | Ignored until the file is saved. | ## Build contexts The primary context is the context used for regular `COPY` and `ADD` sources. | Context kind | Example | Local file inspection | | ----------------------- | --------------------------------------------- | ------------------------ | | Local directory | `context = "."`, `build.context: ./app` | Yes | | Local tar archive | `context = "./context.tar"` | No | | Git repository | `context = "https://github.com/acme/app.git"` | No | | Remote URL | `context = "https://example.com/context"` | No | | Empty context | `context = "-"` | No | | Named image context | `docker-image://alpine:3.20` | No | | Bake target context | `target:base` | No local file inspection | | Compose service context | `service:base` | No local file inspection | Only local directory contexts let tally evaluate `.dockerignore` and read files copied by `COPY` or `ADD`. Remote and non-directory contexts are valid invocation metadata, but tally does not fetch them during linting. Named contexts are tracked separately from the primary context. They are available to rules that need to know that a name was declared, but they do not make files locally readable unless a future rule explicitly supports that context kind. ## Output and attribution When an orchestrator produces more than one invocation, tally keeps diagnostics separate even if multiple invocations point at the same Dockerfile. Text output groups findings by invocation: ```text theme={null} [bake target: api] services/api/Dockerfile:12: buildkit/UndefinedVar - Usage of undefined variable '$NODE_VERSION' [bake target: worker] services/worker/Containerfile:8: tally/max-lines - File has 520 lines, exceeding maximum of 500 Summary: 2 Dockerfiles, 2 invocations, 2 violations. ``` JSON output includes invocation metadata on each violation and reports both file and invocation counts: ```json theme={null} { "files": [ { "file": "services/api/Dockerfile", "violations": [ { "rule": "buildkit/UndefinedVar", "message": "Usage of undefined variable '$NODE_VERSION'", "invocation": { "kind": "bake", "file": "/repo/docker-bake.hcl", "name": "api" } } ] } ], "summary": { "total": 1, "files": 1, "invocations": 1 }, "files_scanned": 1, "invocations_scanned": 1 } ``` SARIF stores invocation metadata in result properties. GitHub Actions annotations prefix the message with the invocation label. Markdown output adds an `Invocation` column. ## Fixes `--fix` is not supported when the lint entrypoint is a Bake or Compose file: ```bash theme={null} tally lint --fix compose.yaml ``` This exits with code `2`. A single Dockerfile can be linted under multiple invocation contexts, and applying one text edit through an orchestrator entrypoint could be wrong for another target or service. To apply fixes, lint the Dockerfile directly: ```bash theme={null} tally lint --fix services/api/Dockerfile ``` Review the fix against the relevant Bake or Compose invocation before committing it. ## LSP and editor use The LSP server remains Dockerfile-document-centric. To attach orchestrator context to Dockerfile diagnostics, configure invocation entrypoints in your editor or LSP client. User-facing setting: ```json theme={null} { "tally.invocationEntrypoints": [ "compose.yaml", "docker-bake.hcl" ] } ``` Protocol payload shape used by LSP clients: ```json theme={null} { "tally": { "workspaces": [ { "uri": "file:///repo", "settings": { "invocationEntrypoints": [ "compose.yaml", "docker-bake.hcl" ] } } ] } } ``` For each open Dockerfile, tally finds invocations from those entrypoints whose resolved Dockerfile path matches the document. Diagnostics use source labels such as `tally/bake:api` or `tally/compose:worker`. Mutating code actions are only shown when the requested diagnostic or range belongs to one invocation context. Broad fix-all actions are disabled when the selected diagnostics span multiple invocation contexts. ## Exit codes Orchestrator entrypoints use the same exit-code family as Dockerfile linting: | Code | Orchestrator meaning | | ---- | ------------------------------------------------------------------------------------------------------ | | `0` | Clean run, or a valid Bake/Compose file with no lintable invocations. | | `1` | Violations were found at or above `--fail-level`. | | `2` | CLI misuse, parse/load failure, unsupported orchestrator feature, or invalid target/service selection. | | `4` | A referenced Dockerfile has fatal syntax errors. | Exit code `3` remains the Dockerfile discovery "no files found" case for directory and glob inputs. A valid orchestrator file with zero buildable targets or services exits `0`, not `3`. # CI/CD integration Source: https://tally.wharflab.com/guides/ci-cd Integrate tally into GitHub Actions, GitLab CI, and pre-commit hooks for automated Dockerfile linting. tally is designed to run fast in CI without requiring Docker Desktop or a daemon. It produces output in formats that native CI systems understand natively, including GitHub Actions annotations and SARIF for code scanning. ## Quick tips * Use `--fail-level` to control which severities fail CI (for example, fail on `warning` but not on `style`). * Use `--exclude` to skip generated or vendor trees. * Commit a `.tally.toml` to keep CI and local runs consistent. * Use `--format github-actions` for inline PR annotations on GitHub. * Use `--format sarif` to upload results to GitHub Code Scanning or Azure DevOps. * Lint `docker-bake.hcl` or `compose.yaml` directly when those files define the real build. ### Basic lint step Add tally to any workflow that touches Dockerfiles: ```yaml theme={null} name: Lint on: push: branches: [main] pull_request: jobs: tally: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install tally run: npm install -g tally-cli - name: Lint Dockerfiles run: tally lint --format github-actions . ``` The `github-actions` format emits `::warning` and `::error` annotations that GitHub renders inline in the PR diff. ### SARIF upload to Code Scanning Upload results to GitHub Code Scanning for a persistent view of findings across commits: ```yaml theme={null} name: Lint on: push: branches: [main] pull_request: schedule: - cron: "0 6 * * 1" # Weekly on Monday jobs: tally: runs-on: ubuntu-latest permissions: security-events: write # Required for SARIF upload steps: - uses: actions/checkout@v4 - name: Install tally run: npm install -g tally-cli - name: Run tally run: | tally lint \ --format sarif \ --output tally.sarif \ --fail-level none \ . - name: Upload SARIF uses: github/codeql-action/upload-sarif@v3 with: sarif_file: tally.sarif ``` Use `--fail-level none` when uploading SARIF so the step doesn't fail before the upload runs. Code Scanning will surface the findings separately. ### Matrix strategy for multiple Dockerfiles Lint different Dockerfiles in parallel using a matrix: ```yaml theme={null} jobs: tally: runs-on: ubuntu-latest strategy: matrix: dockerfile: - Dockerfile - Dockerfile.dev - services/api/Dockerfile steps: - uses: actions/checkout@v4 - name: Install tally run: npm install -g tally-cli - name: Lint ${{ matrix.dockerfile }} run: tally lint --format github-actions ${{ matrix.dockerfile }} ``` ### Lint step ```yaml theme={null} tally:lint: image: node:lts-alpine stage: lint script: - npm install -g tally-cli - tally lint --format text . rules: - changes: - "**Dockerfile*" - "**Containerfile*" - .tally.toml ``` ### With SARIF artifact GitLab supports SARIF reports as Code Quality artifacts: ```yaml theme={null} tally:lint: image: node:lts-alpine stage: lint script: - npm install -g tally-cli - tally lint --format sarif --output gl-sast-report.sarif --fail-level none . || true artifacts: reports: sast: gl-sast-report.sarif when: always expire_in: 1 week ``` ### Using tally as a pre-commit hook Add tally to your `.pre-commit-config.yaml`. Because tally is distributed via npm, pip, and RubyGems, you can use `language: system` with a globally installed binary: ```yaml theme={null} repos: - repo: local hooks: - id: tally name: tally (Dockerfile linter) language: system entry: tally lint args: ["--format", "text"] files: '(Dockerfile|Containerfile)(\..*)?$|.*\.(Dockerfile|Containerfile)$' pass_filenames: true ``` Install pre-commit and the hook: ```bash theme={null} pip install pre-commit npm install -g tally-cli pre-commit install ``` Run manually against all files: ```bash theme={null} pre-commit run tally --all-files ``` ### Fail level for pre-commit To only block commits on errors (not warnings or style issues), add `--fail-level error`: ```yaml theme={null} - id: tally name: tally (Dockerfile linter) language: system entry: tally lint args: ["--fail-level", "error"] files: '(Dockerfile|Containerfile)(\..*)?$|.*\.(Dockerfile|Containerfile)$' pass_filenames: true ``` ## Lint Bake or Compose in CI If your CI builds images through Bake or Compose, lint the same entrypoint rather than rediscovering Dockerfiles: ```bash theme={null} # Docker Buildx Bake tally lint --format github-actions docker-bake.hcl # Docker Compose tally lint --format github-actions compose.yaml ``` Select only the build that changed: ```bash theme={null} tally lint --format github-actions docker-bake.hcl --target api tally lint --format github-actions compose.yaml --service api ``` Do not use `--fix` in orchestrator CI jobs. Orchestrator runs can represent multiple builds of the same Dockerfile, so fixes are only available when linting a Dockerfile directly. See [Build invocations](/guides/build-invocations) for the full behavior. ## Output format recommendations | CI system | Recommended format | Why | | ---------------------------- | -------------------- | ----------------------------------- | | GitHub Actions (annotations) | `github-actions` | Inline PR diff annotations | | GitHub Code Scanning | `sarif` | Persistent findings in Security tab | | GitLab Code Quality | `sarif` | SAST artifact support | | Azure DevOps | `sarif` | SARIF is natively supported | | Terminal / local | `text` (default) | Human-readable with source snippets | | AI agents / scripts | `json` or `markdown` | Machine-readable or token-efficient | ## Related guides * [Configuration](/guides/configuration) — set `fail-level`, `format`, and `exclude` in `.tally.toml` * [Output formats](/guides/output-formats) — full reference for all output formats * [Exit codes](/guides/exit-codes) — how to handle each exit code in scripts # Configuration Source: https://tally.wharflab.com/guides/configuration Complete reference for tally's config file, environment variables, CLI flags, and inline directives. tally supports configuration via TOML config files, environment variables, and CLI flags. Sources cascade in a predictable order so you can set project defaults while allowing per-run overrides. ## Priority order Configuration sources are applied highest-priority first: 1. **CLI flags** — `--fail-level error` 2. **Environment variables** — `TALLY_OUTPUT_FAIL_LEVEL=error` 3. **Config file** — `.tally.toml` or `tally.toml` 4. **Built-in defaults** ## Config file ### File names tally looks for these config file names, in order: 1. `.tally.toml` (hidden file, recommended) 2. `tally.toml` ### Discovery tally uses cascading config discovery similar to [Ruff](https://docs.astral.sh/ruff/configuration/): 1. Starting from the Dockerfile's directory, walks up the filesystem. 2. Stops at the first `.tally.toml` or `tally.toml` found. 3. Uses that config — no merging with parent configs. This allows monorepo setups with per-directory configurations: ```text theme={null} monorepo/ ├── .tally.toml # Default config for most services ├── services/ │ ├── api/ │ │ └── Dockerfile # Uses monorepo/.tally.toml │ └── legacy/ │ ├── .tally.toml # Override for legacy service │ └── Dockerfile # Uses services/legacy/.tally.toml ``` ### Explicit config path Override discovery with `--config`: ```bash theme={null} tally lint --config /path/to/.tally.toml Dockerfile ``` *** ## Config file reference Controls how tally reports violations. ```toml theme={null} [output] format = "text" # text, json, sarif, github-actions, markdown path = "stdout" # stdout, stderr, or a file path show-source = true # Show source code snippets fail-level = "style" # Minimum severity for exit code 1 ``` | Option | Default | Description | | ------------- | ---------- | --------------------------------------------------------------------------------------- | | `format` | `"text"` | Output format: `text`, `json`, `sarif`, `github-actions`, `markdown` | | `path` | `"stdout"` | Output destination: `stdout`, `stderr`, or a file path | | `show-source` | `true` | Show source code snippets alongside violations | | `fail-level` | `"style"` | Minimum severity that produces exit code 1: `error`, `warning`, `info`, `style`, `none` | Controls auto-fix safety when fixes are requested. ```toml theme={null} unsafe-fixes = true ``` | Option | Default | Description | | -------------- | ------- | ------------------------------------------------------- | | `unsafe-fixes` | unset | Enable application of unsafe fixes when `--fix` is used | Controls which rules are enabled and how they are configured. ### Rule selection Use glob patterns to include or exclude rules by namespace or by specific rule code: ```toml theme={null} [rules] include = ["buildkit/*", "tally/*", "hadolint/*"] exclude = [ "buildkit/MaintainerDeprecated", "hadolint/DL3008", ] ``` #### Per-rule configuration Configure individual rules with `severity` and rule-specific options: ```toml theme={null} # Syntax: [rules..] [rules.tally.max-lines] severity = "error" max = 500 skip-blank-lines = true skip-comments = true [rules.buildkit.StageNameCasing] severity = "info" # Downgrade from warning to info [rules.hadolint.DL3026] severity = "warning" trusted-registries = ["docker.io", "gcr.io", "ghcr.io"] ``` #### Severity levels | Severity | Description | | ----------- | ----------------------------------------- | | `"off"` | Disable the rule | | `"error"` | Critical issues that should block CI | | `"warning"` | Important issues that should be addressed | | `"info"` | Informational suggestions | | `"style"` | Style preferences | #### Enabling off-by-default rules Some rules are disabled by default (for example, experimental rules). Enable them by providing configuration: ```toml theme={null} # DL3026 is off by default — providing config auto-enables it [rules.hadolint.DL3026] trusted-registries = ["docker.io", "ghcr.io"] # Or set severity explicitly [rules.tally.prefer-copy-heredoc] severity = "style" ``` Controls how inline ignore comments are processed. ```toml theme={null} [inline-directives] enabled = true # Process inline directives (default: true) warn-unused = false # Warn about unused directives (default: false) validate-rules = false # Warn about unknown rule codes (default: false) require-reason = false # Require reason= on all ignore directives (default: false) ``` | Option | Default | Description | | ---------------- | ------- | -------------------------------------------------------- | | `enabled` | `true` | Process inline directives | | `warn-unused` | `false` | Warn about directives that don't suppress any violations | | `validate-rules` | `false` | Warn about unknown rule codes in directives | | `require-reason` | `false` | Require `reason=` on all ignore directives | Configuration for AI AutoFix via ACP. See [AI AutoFix (ACP)](/guides/ai-autofix) for the full guide. ```toml theme={null} [ai] enabled = false command = ["gemini", "--experimental-acp", "--allowed-mcp-server-names=none", "--model=gemini-3-flash-preview"] timeout = "90s" max-input-bytes = 262144 redact-secrets = true ``` | Setting | Default | Description | | ----------------- | --------- | ---------------------------------------------------- | | `enabled` | `false` | Master kill-switch for AI features | | `command` | *(empty)* | ACP agent argv (stdio). If empty, AI fixes can't run | | `timeout` | `"90s"` | Per-fix timeout for the ACP interaction | | `max-input-bytes` | `262144` | Maximum prompt size to send to the agent | | `redact-secrets` | `true` | Redact obvious secrets in prompts (best-effort) | Pre-parse file validation. Useful for rejecting unexpectedly large files before linting. ```toml theme={null} [file-validation] max-file-size = 102400 # bytes; 0 = unlimited ``` | Option | Default | Description | | --------------- | ----------------- | --------------------------------------------------------------------------------------------------------- | | `max-file-size` | `102400` (100 KB) | Maximum file size in bytes. Files above this limit are rejected before parsing. Set to `0` for unlimited. | Controls registry-aware and other slow checks that require network access. ```toml theme={null} [slow-checks] mode = "auto" # auto, on, off timeout = "20s" fail-fast = true ``` | Option | Default | Description | | ----------- | -------- | ---------------------------------------------------------------------------------------- | | `mode` | `"auto"` | `auto` skips slow checks in CI; `on` always runs them; `off` always skips them | | `timeout` | `"20s"` | Timeout for slow checks | | `fail-fast` | `true` | Skip slow checks for files that already have `error`-severity violations from fast rules | You can also control this via CLI: ```bash theme={null} tally lint --slow-checks=on --slow-checks-timeout=30s Dockerfile ``` *** ## Environment variables | Variable | Description | | -------------------------- | -------------------------------------------------------------------- | | `TALLY_OUTPUT_FORMAT` | Output format: `text`, `json`, `sarif`, `github-actions`, `markdown` | | `TALLY_FORMAT` | Alias for `TALLY_OUTPUT_FORMAT` | | `TALLY_OUTPUT_PATH` | Output destination: `stdout`, `stderr`, or file path | | `TALLY_OUTPUT_SHOW_SOURCE` | Show source snippets: `true` / `false` | | `TALLY_OUTPUT_FAIL_LEVEL` | Minimum severity for non-zero exit | | `NO_COLOR` | Disable colored output (standard env var) | | Variable | Description | | ---------------------------------------- | ------------------------------------------------- | | `TALLY_RULES_MAX_LINES_MAX` | Maximum lines allowed | | `TALLY_RULES_MAX_LINES_SKIP_BLANK_LINES` | Exclude blank lines: `true` / `false` | | `TALLY_RULES_MAX_LINES_SKIP_COMMENTS` | Exclude comment lines: `true` / `false` | | `TALLY_RULES_SELECT` | Enable specific rules (comma-separated patterns) | | `TALLY_RULES_IGNORE` | Disable specific rules (comma-separated patterns) | | Variable | Description | | --------------------------- | -------------------------------------------------------- | | `TALLY_EXCLUDE` | Glob pattern(s) to exclude files (comma-separated) | | `TALLY_CONTEXT` | Build context directory for direct Dockerfile linting | | `TALLY_SLOW_CHECKS` | Slow checks mode: `auto`, `on`, `off` | | `TALLY_SLOW_CHECKS_TIMEOUT` | Timeout for slow checks (e.g. `20s`) | | `TALLY_FIX` | Apply safe fixes automatically: `true` / `false` | | `TALLY_FIX_UNSAFE` | Also apply unsafe fixes: `true` / `false` | | `TALLY_UNSAFE_FIXES` | Config-shaped alias for `unsafe-fixes`: `true` / `false` | | `TALLY_FIX_RULE` | Limit fixes to specific rules (comma-separated) | | Variable | Description | | ---------------------------------------- | -------------------------------------------------------- | | `TALLY_NO_INLINE_DIRECTIVES` | Disable inline directive processing: `true` / `false` | | `TALLY_INLINE_DIRECTIVES_WARN_UNUSED` | Warn about unused directives: `true` / `false` | | `TALLY_INLINE_DIRECTIVES_REQUIRE_REASON` | Require `reason=` on ignore directives: `true` / `false` | | Variable | Description | | -------------------------- | -------------------------------------------------------- | | `TALLY_AI_ENABLED` | Enable AI AutoFix: `true` / `false` | | `TALLY_ACP_COMMAND` | ACP agent command line | | `TALLY_AI_TIMEOUT` | Per-fix timeout (e.g. `90s`) | | `TALLY_AI_MAX_INPUT_BYTES` | Maximum prompt size in bytes | | `TALLY_AI_REDACT_SECRETS` | Redact secrets before sending to agent: `true` / `false` | *** ## CLI flags | Flag | Description | | -------------- | ------------------------------------------------------------------ | | `--config, -c` | Path to config file (overrides discovery) | | `--no-config` | Skip config file discovery and use defaults plus env/CLI overrides | | `--exclude` | Glob pattern(s) to exclude files (repeatable) | | `--context` | Build context directory for direct Dockerfile linting | | `--target` | Bake target or group to lint (repeatable; Bake entrypoints only) | | `--service` | Compose service to lint (repeatable; Compose entrypoints only) | | `--select` | Enable specific rules (repeatable) | | `--ignore` | Disable specific rules (repeatable) | | Flag | Description | | --------------- | -------------------------------------------------------------------- | | `--format, -f` | Output format: `text`, `json`, `sarif`, `github-actions`, `markdown` | | `--output, -o` | Output destination: `stdout`, `stderr`, or file path | | `--no-color` | Disable colored output | | `--show-source` | Show source code snippets (default: true) | | `--hide-source` | Hide source code snippets | | `--fail-level` | Minimum severity for non-zero exit | | Flag | Description | | -------------------- | ----------------------------------------------- | | `--max-lines, -l` | Maximum number of lines allowed (0 = unlimited) | | `--skip-blank-lines` | Exclude blank lines from the line count | | `--skip-comments` | Exclude comment lines from the line count | | Flag | Description | | -------------------------- | ------------------------------------------------------------ | | `--no-inline-directives` | Disable processing of inline ignore directives | | `--warn-unused-directives` | Warn about directives that don't suppress any violations | | `--require-reason` | Warn about ignore directives without a `reason=` explanation | | Flag | Description | | ---------------------- | ------------------------------------------------- | | `--fix` | Apply safe auto-fixes automatically | | `--fix-rule` | Only fix specific rules (repeatable) | | `--fix-unsafe` | Also apply unsafe fixes (requires `--fix`) | | `--ai` | Enable AI AutoFix (requires an ACP agent command) | | `--acp-command` | ACP agent command line | | `--ai-timeout` | Per-fix AI timeout (e.g. `90s`) | | `--ai-max-input-bytes` | Maximum prompt size in bytes | | `--ai-redact-secrets` | Redact secrets before sending to agent | *** ## Build context and invocation flags `--context` applies only when you lint Dockerfiles directly: ```bash theme={null} tally lint --context . Dockerfile ``` When you pass a Bake or Compose file, tally reads the build context from the selected target or service instead: ```bash theme={null} tally lint docker-bake.hcl --target api tally lint compose.yaml --service api ``` Do not combine `--context` with a Bake or Compose entrypoint. Use `--target` only with Bake, and `--service` only with Compose. See [Build invocations](/guides/build-invocations) for the full entrypoint behavior. *** ## Inline directives Suppress specific violations using inline comment directives directly in your Dockerfile. Suppress violations on the **next line**: ```dockerfile theme={null} # tally ignore=StageNameCasing FROM alpine AS Build ``` Suppress multiple rules with comma-separated values: ```dockerfile theme={null} # tally ignore=StageNameCasing,DL3006 FROM Ubuntu AS Build ``` Suppress all rules on a line: ```dockerfile theme={null} # tally ignore=all FROM Ubuntu AS Build ``` Suppress violations throughout the **entire file**: ```dockerfile theme={null} # tally global ignore=max-lines FROM alpine # ... rest of file is not checked for max-lines ``` Document why a rule is suppressed using `;reason=`: ```dockerfile theme={null} # tally ignore=DL3006;reason=Using older base image for compatibility FROM ubuntu:16.04 # tally global ignore=max-lines;reason=Generated file, size is expected ``` Use `--require-reason` (or `require-reason = true` in `.tally.toml`) to enforce that all ignore directives include an explanation. tally supports directive formats from other linters, making migration easy: ```dockerfile theme={null} # hadolint ignore=DL3006 FROM ubuntu # hadolint global ignore=DL3008 FROM alpine # check=skip=StageNameCasing FROM alpine AS Build ``` *** ## Example configurations ### Strict CI ```toml theme={null} # .tally.toml - Strict settings for CI [output] format = "sarif" path = "tally-results.sarif" fail-level = "warning" [rules] include = ["buildkit/*", "tally/*", "hadolint/*"] [rules.tally.max-lines] max = 50 skip-blank-lines = true skip-comments = true [inline-directives] require-reason = true warn-unused = true ``` ### Relaxed development ```toml theme={null} # .tally.toml - Relaxed settings for development [output] format = "text" show-source = true fail-level = "error" [rules] include = ["buildkit/*", "tally/*"] exclude = ["buildkit/MaintainerDeprecated"] [rules.tally.max-lines] severity = "warning" max = 200 ``` ### Monorepo setup Place a root `.tally.toml` with shared defaults, then override for specific services: ```text theme={null} monorepo/ ├── .tally.toml # Shared defaults ├── services/ │ ├── api/ │ │ └── Dockerfile # Inherits root config │ └── legacy/ │ ├── .tally.toml # Legacy overrides │ └── Dockerfile ``` ```toml theme={null} # services/legacy/.tally.toml - Gradual migration from hadolint [output] format = "text" fail-level = "error" [rules] # Start with just BuildKit rules; add hadolint rules gradually include = ["buildkit/*"] [rules.buildkit.StageNameCasing] severity = "info" # Downgrade during migration ``` # Exit codes Source: https://tally.wharflab.com/guides/exit-codes Reference for tally's exit codes and how to handle them in scripts and CI pipelines. tally uses distinct exit codes so scripts and CI pipelines can react to different outcomes with precision. ## Exit code reference | Code | Name | Meaning | | ---- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------- | | `0` | Success | No violations found, all violations are below the configured `--fail-level`, or a valid orchestrator file has no lintable invocations | | `1` | Violations | One or more violations at or above the configured `--fail-level` | | `2` | Error | Configuration, parse, I/O, CLI usage, or unsupported orchestrator error | | `3` | No files | No Dockerfiles to lint from directory or glob discovery (missing file, empty glob, empty directory) | | `4` | Syntax error | Dockerfile has fatal syntax issues (unknown instructions, malformed directives), including Dockerfiles referenced by an orchestrator | ## How `--fail-level` affects exit code 1 By default, tally exits with code `1` when any violation at `style` severity or above is found. Use `--fail-level` to raise or lower that threshold: ```bash theme={null} # Fail only on errors (ignore warnings, info, and style) tally lint --fail-level error Dockerfile # Fail on warnings or worse tally lint --fail-level warning Dockerfile # Never exit 1 due to violations (useful for SARIF upload workflows) tally lint --fail-level none --format sarif . > results.sarif # Default: fail on any violation including style issues tally lint --fail-level style Dockerfile ``` Available levels from most to least severe: `error`, `warning`, `info`, `style` (default), `none`. ## Orchestrator entrypoints Bake and Compose entrypoints use the same exit-code family, with two differences from directory discovery: * A valid Bake or Compose file with no lintable targets or services exits `0`, not `3`. * Invalid target/service selection, unsupported inline Dockerfiles, profile-gated build services, multi-file Bake setups, and invalid flag combinations exit `2`. Examples: ```bash theme={null} # Code 0 when compose.yaml is valid but only contains image-only services tally lint compose.yaml # Code 2: --target is only valid for Bake entrypoints tally lint compose.yaml --target api # Code 2: --fix is not supported for orchestrator entrypoints tally lint --fix docker-bake.hcl ``` See [Build invocations](/guides/build-invocations) for supported entrypoint behavior. ## Script examples ### Basic check ```bash theme={null} tally lint . echo $? # 0 = clean, 1 = violations found ``` ### Handling each exit code ```bash theme={null} tally lint Dockerfile.prod status=$? case "$status" in 0) echo "No issues found." ;; 1) echo "Violations found — review and fix before merging."; exit 1 ;; 2) echo "Config or parse error — check your .tally.toml."; exit 2 ;; 3) echo "No Dockerfiles found — check the path."; exit 3 ;; 4) echo "Dockerfile has syntax errors — fix before linting."; exit 4 ;; *) echo "Unknown exit code: $status"; exit "$status" ;; esac ``` ### Distinguish "nothing to lint" from real errors Exit code `3` lets you detect a missing or empty path separately from a configuration error (code `2`): ```bash theme={null} tally lint Dockerfile.prod status=$? if [ "$status" -eq 3 ]; then echo "No Dockerfiles found — skipping lint step." elif [ "$status" -ne 0 ]; then exit "$status" fi ``` ### Detect syntax errors before linting ```bash theme={null} tally lint Dockerfile status=$? if [ "$status" -eq 4 ]; then echo "Dockerfile has fatal syntax errors — fix typos or invalid instructions first." exit 4 fi ``` ### CI: fail only on errors ```bash theme={null} # In CI, treat warnings as informational rather than blocking tally lint --fail-level error --format github-actions . ``` ## CI/CD tips * Use `--fail-level error` to allow warnings without blocking the build. * Use `--fail-level none` when uploading SARIF so the upload step always runs even when violations exist. * Exit code `3` distinguishes "the path was wrong" from "the config is broken" (code `2`), useful in matrix CI jobs where not every service has a Dockerfile. * Exit code `4` indicates the Dockerfile itself is malformed — fix those before addressing lint violations. See [CI/CD integration](/guides/ci-cd) for complete pipeline examples. # IDE integration Source: https://tally.wharflab.com/guides/ide-integration Set up stack-aware Dockerfile linting, formatting, and auto-fix in VS Code, JetBrains IDEs, and any LSP-compatible editor. tally provides real-time diagnostics in your editor via a built-in Language Server Protocol (LSP) server. Official extensions are available for VS Code and JetBrains IDEs. Any other editor that supports LSP can connect using `tally lsp --stdio`. Install the official `wharflab.tally` extension from the Visual Studio Marketplace. Install Tally in PhpStorm, RubyMine, Rider, and other JetBrains IDEs. Run `tally lsp --stdio` to connect any LSP-compatible editor. *** ## VS Code Install the official extension from the Visual Studio Marketplace: **Extension ID:** `wharflab.tally` **[Install from Marketplace](https://marketplace.visualstudio.com/items?itemName=wharflab.tally)** The extension provides: * Real-time linting diagnostics as you type. * Inline violation messages with rule codes and doc links. * Squiggles and Problems panel integration. * Picks up `.tally.toml` config files automatically from your workspace. The extension uses `tally lsp --stdio` under the hood. Marketplace packages include a platform-specific `tally` binary, which is used automatically when no compatible project or environment installation is available. *** ## JetBrains IDEs Install the official plugin from JetBrains Marketplace: **Plugin ID:** `30255-tally` **[Install from JetBrains Marketplace](https://plugins.jetbrains.com/plugin/30255-tally)** The plugin works in IntelliJ-based IDEs including **PhpStorm**, **RubyMine**, Rider, IntelliJ IDEA, GoLand, PyCharm, and WebStorm. It provides: * Stack-aware Dockerfile diagnostics for PHP, Ruby, PowerShell, shell, and Windows containers. * Modern BuildKit guidance for heredocs, cache mounts, multi-stage builds, and security. * Safe quick fixes, rule documentation links, and Fix All on Save. * Dockerfile and Containerfile formatting through **Reformat Code**. * Automatic `.tally.toml` / `tally.toml` discovery and a bundled Tally binary. PhpStorm showing Tally diagnostics for Composer production dependencies, OPcache, Xdebug, and package cache mounts PhpStorm intention menu showing Tally quick fixes and documentation actions for PHP Dockerfile rules RubyMine showing Tally diagnostics for Bundler deployment mode, Rails asset compilation, health checks, and YJIT ### Stack-specific analysis Tally analyzes the tools and runtimes used by each stage rather than treating every `RUN` instruction as generic shell text. * **PhpStorm:** Composer production dependencies, OPcache, Xdebug, PHP package installs, cache mounts, and security. * **RubyMine:** Bundler deployment mode and cache mounts, Rails asset compilation and health checks, YJIT, runtime state paths, and secrets. * **Rider and Windows projects:** PowerShell semantics, Windows container behavior, effective `SHELL`, and PowerShell security rules. BuildKit and Hadolint-compatible checks remain available alongside these stack-specific rules. ### Configure the plugin Open **Settings → Tools → Tally** to configure the executable, an explicit configuration file, unsafe fixes, formatting, and Fix All on Save. The plugin discovers the nearest `.tally.toml` or `tally.toml` by default. *** ## Invocation-aware diagnostics By default, the LSP server lints the Dockerfile document you are editing. If your workspace builds that Dockerfile through Bake or Compose, configure invocation entrypoints so diagnostics use the same build args, target stage, platform, and build context as the real build. User-facing setting: ```json theme={null} { "tally.invocationEntrypoints": ["compose.yaml", "docker-bake.hcl"] } ``` For generic LSP clients that send the full tally settings envelope, the payload shape is: ```json theme={null} { "tally": { "workspaces": [ { "uri": "file:///repo", "settings": { "invocationEntrypoints": ["compose.yaml", "docker-bake.hcl"] } } ] } } ``` When a configured entrypoint resolves to the open Dockerfile, diagnostics use source labels such as `tally/bake:api` or `tally/compose:worker`. If a code action request spans diagnostics from more than one invocation context, mutating quick fixes and fix-all actions are hidden. This prevents one edit from being applied as if it were valid for every target or service. See [Build invocations](/guides/build-invocations) for CLI behavior and orchestrator limitations. *** ## Generic LSP Any editor that supports the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) can use tally's built-in LSP server over stdio. ### Start the LSP server If tally is installed globally: ```bash theme={null} tally lsp --stdio ``` If you prefer to use the npm package without a global install: ```bash theme={null} npx -y tally-cli lsp --stdio ``` ### Editor configuration examples The exact configuration depends on your editor's LSP client. The general pattern is to configure your LSP client to run `tally lsp --stdio` for Dockerfile files (`dockerfile` language ID). ```lua Neovim (nvim-lspconfig) theme={null} -- In your Neovim config (lua) require('lspconfig').tally.setup({ cmd = { 'tally', 'lsp', '--stdio' }, filetypes = { 'dockerfile' }, root_dir = require('lspconfig.util').root_pattern('.tally.toml', 'tally.toml', '.git'), }) ``` ```toml Helix (languages.toml) theme={null} [[language]] name = "dockerfile" language-servers = ["tally"] [language-server.tally] command = "tally" args = ["lsp", "--stdio"] ``` ```json Zed (settings.json) theme={null} { "lsp": { "tally": { "binary": { "path": "tally", "arguments": ["lsp", "--stdio"] } } } } ``` tally's LSP server uses cascading config discovery — it finds the nearest `.tally.toml` relative to each Dockerfile being edited, the same way the CLI does. # Installing BuildKit for Windows Containers Source: https://tally.wharflab.com/guides/installing-buildkit-windows-containers Set up a standalone BuildKit daemon for Windows containers and connect docker buildx with the remote driver. This guide sets up a standalone BuildKit daemon for Windows containers and connects `docker buildx` to it with the `remote` driver. It is based on a working setup validated on **April 1, 2026** with real Windows container builds. ## Known-good versions * `containerd v1.7.30` * `buildkit v0.29.0` * `buildx v0.33.0` Do **not** use `containerd v2.2.2` for this setup. In our Windows validation, `buildkitd` failed to start against it with: ```text theme={null} unknown service containerd.services.leases.v1.Leases ``` ## Prerequisites * Windows with Docker Desktop installed * Docker Desktop switched to **Windows containers** * An **elevated PowerShell** session * Windows container prerequisites enabled (`Hyper-V`, Windows Containers, and the default `nat` network) ## Install standalone BuildKit Run this in an elevated PowerShell window: ```powershell theme={null} $ErrorActionPreference = 'Stop' $containerdVersion = '1.7.30' $buildkitVersion = 'v0.29.0' $buildxVersion = 'v0.33.0' $arch = 'amd64' $temp = Join-Path $env:TEMP 'buildkit-win-upgrade' New-Item -ItemType Directory -Force -Path $temp | Out-Null Set-Location $temp Write-Host 'Checking nat network...' $nat = Get-HnsNetwork | Where-Object { $_.Name -eq 'nat' } if ($null -eq $nat) { throw "NAT network not found. Make sure Windows Containers / Hyper-V are enabled and Docker is in Windows containers mode." } $gateway = $nat.Subnets[0].GatewayAddress $subnet = $nat.Subnets[0].AddressPrefix Write-Host 'Installing CNI config...' $cniConfPath = "$env:ProgramFiles\containerd\cni\conf" $cniBinDir = "$env:ProgramFiles\containerd\cni\bin" $cniVersion = '0.3.0' New-Item -ItemType Directory -Force -Path $cniConfPath, $cniBinDir | Out-Null curl.exe -fSL "https://github.com/microsoft/windows-container-networking/releases/download/v$cniVersion/windows-container-networking-cni-amd64-v$cniVersion.zip" -o "windows-container-networking-cni-amd64-v$cniVersion.zip" tar.exe xvf "windows-container-networking-cni-amd64-v$cniVersion.zip" -C $cniBinDir $natConfig = @" { "cniVersion": "$cniVersion", "name": "nat", "type": "nat", "master": "Ethernet", "ipam": { "subnet": "$subnet", "routes": [ { "gateway": "$gateway" } ] }, "capabilities": { "portMappings": true, "dns": true } } "@ Set-Content -Path "$cniConfPath\0-containerd-nat.conf" -Value $natConfig Write-Host 'Installing containerd...' curl.exe -fSL "https://github.com/containerd/containerd/releases/download/v$containerdVersion/containerd-$containerdVersion-windows-$arch.tar.gz" -o "containerd-$containerdVersion-windows-$arch.tar.gz" tar.exe xvf "containerd-$containerdVersion-windows-$arch.tar.gz" if (Get-Service containerd -ErrorAction SilentlyContinue) { Stop-Service containerd -ErrorAction SilentlyContinue .\bin\containerd.exe --unregister-service } .\bin\containerd.exe --register-service Start-Service containerd Write-Host 'Installing BuildKit...' curl.exe -fSL "https://github.com/moby/buildkit/releases/download/$buildkitVersion/buildkit-$buildkitVersion.windows-$arch.tar.gz" -o "buildkit-$buildkitVersion.windows-$arch.tar.gz" tar.exe xvf "buildkit-$buildkitVersion.windows-$arch.tar.gz" $buildkitPath = "$env:ProgramFiles\buildkit" New-Item -ItemType Directory -Force -Path $buildkitPath | Out-Null Copy-Item -Path ".\bin\*" -Destination $buildkitPath -Force if (Get-Service buildkitd -ErrorAction SilentlyContinue) { Stop-Service buildkitd -ErrorAction SilentlyContinue & "$buildkitPath\buildkitd.exe" --unregister-service } Write-Host 'Registering buildkitd service...' & "$buildkitPath\buildkitd.exe" --register-service ` --addr "npipe:////./pipe/buildkitd" ` --containerd-cni-config-path="$cniConfPath\0-containerd-nat.conf" ` --containerd-cni-binary-dir="$cniBinDir" Start-Service buildkitd Write-Host 'Installing buildx plugin...' $pluginsDir = "$env:USERPROFILE\.docker\cli-plugins" New-Item -ItemType Directory -Force -Path $pluginsDir | Out-Null curl.exe -fSL "https://github.com/docker/buildx/releases/download/$buildxVersion/buildx-$buildxVersion.windows-$arch.exe" -o "$pluginsDir\docker-buildx.exe" Write-Host 'Waiting for buildkitd...' for ($i = 0; $i -lt 30; $i++) { Start-Sleep -Seconds 2 & "$buildkitPath\buildctl.exe" --addr "npipe:////./pipe/buildkitd" debug info *> $null if ($LASTEXITCODE -eq 0) { break } } & "$buildkitPath\buildctl.exe" --addr "npipe:////./pipe/buildkitd" debug info Write-Host 'Creating buildx builder...' docker buildx rm buildkit-windows 2>$null docker buildx create --name buildkit-windows --driver remote "npipe:////./pipe/buildkitd" --use docker buildx inspect --bootstrap buildkit-windows docker buildx version ``` ## Allow non-admin clients to use the BuildKit pipe If `buildctl` works in the elevated shell but fails in a normal shell with `Access is denied`, re-register `buildkitd` like this: ```powershell theme={null} Stop-Service buildkitd & "$env:ProgramFiles\buildkit\buildkitd.exe" --unregister-service & "$env:ProgramFiles\buildkit\buildkitd.exe" --register-service ` --addr "npipe:////./pipe/buildkitd" ` --group "Users" ` --containerd-cni-config-path="$env:ProgramFiles\containerd\cni\conf\0-containerd-nat.conf" ` --containerd-cni-binary-dir="$env:ProgramFiles\containerd\cni\bin" Start-Service buildkitd ``` ## Verify the builder Run these in a normal PowerShell session: ```powershell theme={null} docker buildx version docker buildx inspect buildkit-windows --bootstrap & "$env:ProgramFiles\buildkit\buildctl.exe" --addr "npipe:////./pipe/buildkitd" debug info ``` Expected checks: * `docker buildx version` shows `v0.33.0` * `docker buildx inspect buildkit-windows --bootstrap` shows `Driver: remote` * the node reports `BuildKit version: v0.29.0` * `buildctl debug info` succeeds through `\\.\pipe\buildkitd` ## Smoke test with a real Windows build ```powershell theme={null} $testDir = Join-Path $env:TEMP 'buildkit-win-test' New-Item -ItemType Directory -Force -Path $testDir | Out-Null @' FROM mcr.microsoft.com/windows/nanoserver:ltsc2025 RUN cmd /S /C echo upgraded-buildkit> C:\buildkit.txt '@ | Set-Content -Path (Join-Path $testDir 'Dockerfile') docker buildx build --builder buildkit-windows --platform windows/amd64 --progress plain --load -t bk-win-test $testDir docker run --rm bk-win-test cmd /S /C type C:\buildkit.txt ``` If the setup is healthy, the container prints: ```text theme={null} upgraded-buildkit ``` ## Troubleshooting `buildkitd` fails to start with `unknown service containerd.services.leases.v1.Leases` * You are likely using an incompatible `containerd` version such as `2.2.2`. * Reinstall `containerd v1.7.30`. `buildx inspect buildkit-windows --bootstrap` times out * Check that the `buildkitd` service is running. * Check that `buildctl debug info` works from the same shell. * If only the elevated shell works, re-register `buildkitd` with `--group "Users"`. `buildctl` says `Access is denied` * The named pipe permissions are too restrictive. * Re-register `buildkitd` with `--group "Users"` as shown above. Windows build starts but fails during execution * Make sure Docker Desktop is actually in Windows containers mode. * Confirm the builder reports `Platforms: windows/amd64`. * Re-run the smoke test before debugging your own Dockerfile. # Output formats Source: https://tally.wharflab.com/guides/output-formats Reference for all five tally output formats: text, json, sarif, github-actions, and markdown. tally supports five output formats so it fits into both terminals and automation pipelines. Select a format with `--format` or the `format` key in `.tally.toml`. ## Output options | Flag | Description | | --------------- | -------------------------------------------------------------------- | | `--format, -f` | Output format: `text`, `json`, `sarif`, `github-actions`, `markdown` | | `--output, -o` | Output destination: `stdout`, `stderr`, or a file path | | `--no-color` | Disable colored output (also respects the `NO_COLOR` env var) | | `--show-source` | Show source code snippets (default: `true`) | | `--hide-source` | Hide source code snippets | *** ## Invocation-aware output When you lint a Bake or Compose entrypoint, tally may run more than one invocation for the same Dockerfile. Output formats preserve that attribution: | Format | Invocation behavior | | ---------------- | -------------------------------------------------------------------------------------------------------------------------- | | `text` | Groups findings under labels such as `[bake target: api]` or `[compose service: worker]` and prints an invocation summary. | | `json` | Adds an `invocation` object to each orchestrator-derived violation and includes `invocations_scanned`. | | `sarif` | Stores invocation metadata in each result's properties. | | `github-actions` | Prefixes annotation messages with the invocation label. | | `markdown` | Adds an `Invocation` column when invocation metadata is present. | See [Build invocations](/guides/build-invocations) for CLI examples and supported entrypoints. *** ## text (default) Human-readable output with colors and source code snippets. Best for local development. ```bash theme={null} tally lint Dockerfile ``` Example output: ```text theme={null} WARNING: StageNameCasing - https://docs.docker.com/go/dockerfile/rule/stage-name-casing/ Stage name 'Builder' should be lowercase Dockerfile:2 ──────────────────── 1 │ FROM alpine >>>2 │ FROM ubuntu AS Builder 3 │ RUN echo "hello" ──────────────────── ``` ### Source snippets Source snippets are shown by default. Toggle them with flags: ```bash theme={null} # Hide source snippets (cleaner for long files) tally lint --hide-source Dockerfile # Explicitly show (useful when config sets show-source = false) tally lint --show-source Dockerfile ``` In `.tally.toml`: ```toml theme={null} [output] show-source = false ``` ## json Machine-readable format with full violation details, summary statistics, and scan metadata. Best for scripts, dashboards, and custom reporting. ```bash theme={null} tally lint --format json Dockerfile ``` Example output: ```json theme={null} { "files": [ { "file": "Dockerfile", "violations": [ { "location": { "file": "Dockerfile", "start": { "line": 2, "column": 0 } }, "rule": "buildkit/StageNameCasing", "message": "Stage name 'Builder' should be lowercase", "severity": "warning", "docUrl": "https://docs.docker.com/go/dockerfile/rule/stage-name-casing/" } ] } ], "summary": { "total": 1, "errors": 0, "warnings": 1, "info": 0, "style": 0, "files": 1 }, "files_scanned": 1, "rules_enabled": 41 } ``` Top-level fields: | Field | Description | | --------------------- | ------------------------------------------------------------------------------------ | | `files` | Array of files with their violations | | `summary` | Aggregate counts: `total`, `errors`, `warnings`, `info`, `style`, `files` | | `files_scanned` | Total number of files scanned | | `invocations_scanned` | Total number of build invocations scanned; omitted or `0` for direct Dockerfile runs | | `rules_enabled` | Number of active rules with a non-`"off"` default severity | Orchestrator-derived violations also include: | Field | Description | | ----------------- | ----------------------------------------- | | `invocation.kind` | `bake` or `compose` | | `invocation.file` | Absolute path to the Bake or Compose file | | `invocation.name` | Target or service name | Write JSON to a file: ```bash theme={null} tally lint --format json --output results.json . ``` ## sarif [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/) format for static analysis tools and CI code scanning integrations. Best for GitHub Code Scanning, Azure DevOps, and similar platforms. ```bash theme={null} tally lint --format sarif --output results.sarif . ``` Or redirect to a file: ```bash theme={null} tally lint --format sarif . > results.sarif ``` SARIF output includes rule metadata, help URIs, and per-result location data that code scanning tools use to render findings in pull requests and security dashboards. ### GitHub Code Scanning ```bash theme={null} # Generate SARIF (use --fail-level none so the step doesn't fail before upload) tally lint --format sarif --output tally.sarif --fail-level none . # Upload with the CodeQL action # uses: github/codeql-action/upload-sarif@v3 # with: # sarif_file: tally.sarif ``` See [CI/CD integration](/guides/ci-cd) for a complete GitHub Actions workflow example. ## github-actions Emits GitHub Actions [workflow commands](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions) (`::warning` and `::error`) for inline PR annotations. ```bash theme={null} tally lint --format github-actions . ``` Example output: ```text theme={null} ::warning file=Dockerfile,line=2,title=StageNameCasing::Stage name 'Builder' should be lowercase ``` GitHub renders these as inline annotations in the PR diff and in the Actions run summary. No upload step is needed — the annotations appear automatically when the format is used in a GitHub Actions workflow. Severity mapping: | tally severity | GitHub command | | -------------- | -------------- | | `error` | `::error` | | `warning` | `::warning` | | `info` | `::notice` | | `style` | `::notice` | ## markdown Concise Markdown tables optimized for AI agents, PR comments, and token-efficient reporting. ```bash theme={null} tally lint --format markdown Dockerfile ``` Example output: ```markdown theme={null} **2 issues** in `Dockerfile` | Line | Issue | | ---- | ------------------------------------------- | | 2 | ⚠️ Stage name 'Builder' should be lowercase | | 10 | ❌ Use absolute WORKDIR | ``` Features: * Summary line with total issue count upfront. * Violations sorted by invocation, file, line, column, and rule code. * Emoji severity indicators: ❌ error, ⚠️ warning, ℹ️ info, 💅 style. * No rule codes or doc URLs — optimized for token efficiency. * Adds a `File` column automatically when linting multiple files. * Adds an `Invocation` column automatically when linting Bake or Compose entrypoints. Pipe output into a file for use as a PR comment or report artifact: ```bash theme={null} tally lint --format markdown . > lint-report.md ``` # Installation Source: https://tally.wharflab.com/installation Install tally via Homebrew, mise, WinGet, npm, Bun, uv, pip, RubyGems, Docker, Go, or from source. tally is a single self-contained binary. Pick the method that fits your environment. ```bash Homebrew theme={null} brew trust --formula wharflab/tap/tally brew install --require-sha wharflab/tap/tally ``` ```bash mise theme={null} mise use -g github:wharflab/tally@latest ``` ```powershell WinGet theme={null} winget install --id Wharflab.Tally ``` ```bash npm theme={null} npm install -g tally-cli ``` ```bash Bun theme={null} bun add -g tally-cli ``` ```bash uv theme={null} uv tool install tally-cli ``` ```bash pip theme={null} pip install tally-cli ``` ```bash RubyGems theme={null} gem install tally-cli ``` ```bash Go theme={null} go install github.com/wharflab/tally@latest ``` ## Homebrew (macOS/Linux) ```bash theme={null} brew trust --formula wharflab/tap/tally brew install --require-sha wharflab/tap/tally ``` Run `tally register-docker-plugin` if you want Docker to discover the plugin without editing `~/.docker/config.json`. Homebrew also installs a `docker-lint` symlink under Homebrew's Docker plugin directory. See [Docker CLI plugin](/integrations/docker-cli-plugin) if you prefer to configure Docker's `cliPluginsExtraDirs`. ## mise ```bash theme={null} mise use -g github:wharflab/tally@latest ``` This installs the latest tally release from GitHub and exposes the `tally` binary through mise's global tool shims. Run `tally register-docker-plugin` after installation to register the plugin with Docker. ## WinGet (Windows) ```powershell theme={null} winget install --id Wharflab.Tally ``` Run `tally register-docker-plugin` after installation to register the plugin with Docker. ## npm ```bash theme={null} npm install -g tally-cli ``` Use a global npm install for Docker CLI plugin setup. Project-local `node_modules` installs are rejected by `tally register-docker-plugin`. ## Bun ```bash theme={null} bun add -g tally-cli ``` Use a global Bun install for Docker CLI plugin setup. `bunx` and project-local `node_modules` launches are rejected by `tally register-docker-plugin`. ## uv ```bash theme={null} uv tool install tally-cli ``` Use `uv tool install` for Python-managed Docker CLI plugin setup. Active Python virtual environments are rejected by `tally register-docker-plugin`. ## pip ```bash theme={null} pip install tally-cli ``` ## RubyGems ```bash theme={null} gem install tally-cli ``` ## Docker / Podman Official images are published to GitHub Container Registry at `ghcr.io/wharflab/tally`. All images are signed with [cosign](https://github.com/sigstore/cosign) (keyless/OIDC). The Linux image is distroless, non-root, and shell-free. It contains only the `tally` binary. The Windows image is built on Nano Server. ### Image tags | Tag | Description | | ------------------------------------------------- | ----------------------------------------------------------------------------------------- | | `ghcr.io/wharflab/tally:latest` | Multi-platform image index: auto-selects `linux/amd64`, `linux/arm64`, or `windows/amd64` | | `ghcr.io/wharflab/tally:v` | Versioned multi-platform image index (e.g., `v1.2.3`) | | `ghcr.io/wharflab/tally:v-linux-amd64` | Per-platform tag | | `ghcr.io/wharflab/tally:v-linux-arm64` | Per-platform tag | | `ghcr.io/wharflab/tally:v-windows-amd64` | Per-platform tag | ### Docker (Linux) ```bash theme={null} # Check the version docker run --rm ghcr.io/wharflab/tally:latest version # Lint a Dockerfile in the current directory docker run --rm -v "$PWD:/work" -w /work ghcr.io/wharflab/tally:latest lint Dockerfile ``` ### Podman (Linux) ```bash theme={null} # Check the version podman run --rm ghcr.io/wharflab/tally:latest version # Lint all Dockerfiles recursively (note the :Z label for SELinux) podman run --rm -v "$PWD:/work:Z" -w /work ghcr.io/wharflab/tally:latest lint . ``` ### Docker (Windows containers) ```powershell theme={null} # Check the version docker run --rm ghcr.io/wharflab/tally:latest version # Lint a Dockerfile docker run --rm -v "${PWD}:C:\work" -w C:\work ghcr.io/wharflab/tally:latest lint Dockerfile ``` ### Verify image signatures All published images are signed with cosign via keyless/OIDC. Verify before use: ```bash theme={null} cosign verify --certificate-identity-regexp='https://github.com/wharflab/tally' \ --certificate-oidc-issuer='https://token.actions.githubusercontent.com' \ ghcr.io/wharflab/tally:latest ``` ## Go Requires Go 1.21 or later. ```bash theme={null} go install github.com/wharflab/tally@latest ``` ## From source ```bash theme={null} git clone https://github.com/wharflab/tally.git cd tally go build . ``` ## Verify your installation After installing, confirm tally is on your `PATH`: ```bash theme={null} tally --help tally lint --help ``` To enable the Docker CLI plugin, run the registration command from a global tally install: ```bash theme={null} tally register-docker-plugin ``` Then verify that Docker can discover it: ```bash theme={null} docker lint --help ``` Check the installed version: ```bash theme={null} # Human-readable tally version # Machine-readable JSON (includes ShellCheck version) tally version --json ``` If the command is not found, make sure the install location is in your `PATH` (for Go installs, this is usually `$GOPATH/bin` or `$HOME/go/bin`). Run `tally lint .` from your project root to immediately lint every Dockerfile and Containerfile in the repo. # Docker CLI plugin Source: https://tally.wharflab.com/integrations/docker-cli-plugin Run tally as docker lint from the Docker CLI. The Docker CLI plugin lets you run `tally lint` as `docker lint`. It is a client-side Docker CLI plugin, not a Docker Engine plugin, so it does not install anything into the Docker daemon. `docker lint` maps to the lint command only. Use `tally lsp --stdio` for editor integrations and `tally version` for full standalone version details. ## Install Install tally globally, make sure `docker info` works, then ask tally to register the `docker-lint` plugin with Docker. ```bash Homebrew theme={null} brew trust --formula wharflab/tap/tally brew install --require-sha wharflab/tap/tally tally register-docker-plugin ``` ```bash mise theme={null} mise use -g github:wharflab/tally@latest tally register-docker-plugin ``` ```powershell WinGet theme={null} winget install --id Wharflab.Tally tally register-docker-plugin ``` ```bash npm theme={null} npm install -g tally-cli tally register-docker-plugin ``` ```bash Bun theme={null} bun add -g tally-cli tally register-docker-plugin ``` ```bash uv theme={null} uv tool install tally-cli tally register-docker-plugin ``` The command refuses project-local or temporary launches such as `node_modules`, `bunx`, an active Python virtual environment, and `go run`. Use a global install before running it. During registration, tally runs `docker info --format json`, checks the Docker CLI version, inspects existing Docker CLI plugins, and verifies the registration after writing `docker-lint`. If Docker already has a non-tally `lint` plugin, tally stops instead of shadowing it. If an older tally plugin is present, tally reports the upgrade. If a newer tally plugin is already registered, tally does not overwrite it unless you pass `--force`. ```text theme={null} Found Docker CLI version 29.4.1 Upgrading tally lint plugin from version 0.5.6 to 0.7.19 ``` Verify the plugin: ```bash theme={null} docker lint --help ``` Use `--dry-run` to inspect the Docker CLI version, source, target path, and existing plugin decision before making changes: ```bash theme={null} tally register-docker-plugin --dry-run ``` ## Manual fallback If you cannot use `tally register-docker-plugin`, register the same binary as `docker-lint`. ```bash macOS/Linux theme={null} mkdir -p ~/.docker/cli-plugins ln -sf "$(command -v tally)" ~/.docker/cli-plugins/docker-lint docker lint --help ``` ```powershell Windows theme={null} New-Item -ItemType Directory -Force "$env:USERPROFILE\.docker\cli-plugins" Copy-Item (Get-Command tally.exe).Source "$env:USERPROFILE\.docker\cli-plugins\docker-lint.exe" -Force docker lint --help ``` On Windows, repeat the copy after upgrading tally unless your installer manages the plugin file for you. Homebrew also installs a `docker-lint` symlink under its own Docker plugin directory. If you prefer to use that Homebrew-managed file, add Homebrew's plugin directory to `~/.docker/config.json`: ```json theme={null} { "cliPluginsExtraDirs": [ "/opt/homebrew/lib/docker/cli-plugins" ] } ``` Use `/usr/local/lib/docker/cli-plugins` on Intel macOS, or the matching Linuxbrew prefix on Linux. ## Usage Use `docker lint` the same way you use `tally lint`: ```bash theme={null} docker lint Dockerfile docker lint . docker lint --format json Dockerfile docker lint --fix Dockerfile docker lint docker-bake.hcl --target web docker lint compose.yaml --service api ``` Docker global flags go before `lint`. tally flags go after `lint`: ```bash theme={null} docker --context production lint Dockerfile docker lint --context . Dockerfile ``` The first command selects Docker's current context. The second command passes tally's build context option. ## Troubleshooting ### `docker: 'lint' is not a docker command` Docker did not find `docker-lint`. Check that the file exists in one of Docker's CLI plugin directories or in a directory listed by `cliPluginsExtraDirs`. ```bash theme={null} ls ~/.docker/cli-plugins/docker-lint ``` Run `tally register-docker-plugin --dry-run` to confirm where tally will register `docker-lint`. ### Invalid plugin metadata Run the metadata command directly: ```bash theme={null} ~/.docker/cli-plugins/docker-lint docker-cli-plugin-metadata ``` The output should be JSON with `"SchemaVersion": "0.1.0"` and `"Vendor": "Wharflab"`. If it prints normal tally help instead, the file is not named `docker-lint`. ### Wrong architecture binary Use a tally binary that matches your operating system and CPU architecture. This matters when copying binaries manually between machines or between Intel and Apple Silicon Macs. ### Missing execute bit on macOS/Linux Make the plugin executable: ```bash theme={null} chmod +x ~/.docker/cli-plugins/docker-lint ``` ### Command name conflict `lint` is a generic plugin command name. If another `docker-lint` exists earlier in Docker's plugin search path, Docker may run that plugin instead. Remove the conflicting file or adjust `cliPluginsExtraDirs`. ## Limitations `docker lint` exposes linting only. It does not expose `tally lsp` or `tally version` as Docker subcommands. WinGet exposes the `tally` command only. Run `tally register-docker-plugin` after installing or upgrading tally through WinGet. # EditorConfig integration Source: https://tally.wharflab.com/integrations/editorconfig Use EditorConfig settings to keep tally formatting fixes aligned with your repository style. tally reads `.editorconfig` for formatting rules that need repository style preferences. Use `.editorconfig` for shared whitespace and indentation settings, and use `.tally.toml` for enabling, disabling, or changing the severity of lint rules. This page will collect tally features that integrate with EditorConfig as they are added. ## Recommended Dockerfile settings Add this section to your repository's `.editorconfig` to keep Dockerfiles and Containerfiles consistent: ```ini theme={null} [{Containerfile,Containerfile.,Dockerfile,Dockerfile.}*] indent_style = tab end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true ``` The pattern matches `Dockerfile`, `Dockerfile.*`, `Containerfile`, and `Containerfile.*`. ## Rules that use EditorConfig * [`tally/prefer-formatted-heredocs`](/rules/tally/prefer-formatted-heredocs) formats `COPY` and `ADD` heredoc bodies for JSON, YAML, TOML, XML, and INI files, plus shell heredocs from executable `RUN` heredocs and `COPY` heredocs with a shell shebang or `.sh` destination. PowerShell heredocs are formatted through PSScriptAnalyzer and do not use EditorConfig today. For structured heredoc payloads, tally resolves EditorConfig settings using a virtual filename next to the Dockerfile and the heredoc destination basename. For example, a heredoc in `services/api/Dockerfile` targeting `/etc/app/config.yaml` is resolved as `services/api/config.yaml`, so `*.yaml` and `config.yaml` sections apply. `COPY` shell heredocs use that same destination-basename lookup. For example, a heredoc targeting `/usr/local/bin/entrypoint.sh` is resolved as `entrypoint.sh`, so `*.sh` and `entrypoint.sh` sections apply. For `RUN` shell heredocs, tally uses a virtual filename next to the Dockerfile named `Dockerfile.heredoc.`, such as `Dockerfile.heredoc.sh`, `Dockerfile.heredoc.bash`, or `Dockerfile.heredoc.zsh`. ## Supported Properties The heredoc formatter currently reads these EditorConfig properties: | Property | Behavior | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `indent_style` | Uses tabs when set to `tab`; otherwise uses spaces. | | `indent_size` | Sets the number of spaces per indent level when it is a positive integer. | | `tab_width` | Used as the fallback indent width when `indent_size` is not a positive integer. | | `max_line_length` | YAML and shell heredocs. For YAML, a positive integer is used as the preferred scalar wrapping width. For shell heredocs, a positive integer is used as the preferred command wrapping width; unset and invalid values use `100`, and `off` disables the shell line-width pass. This is a formatting preference, not a hard maximum. | | `binary_next_line` | Shell heredocs only. Mirrors `shfmt --binary-next-line`. | | `switch_case_indent` | Shell heredocs only. Mirrors `shfmt --case-indent`. | | `space_redirects` | Shell heredocs only. Mirrors `shfmt --space-redirects`. | | `keep_padding` | Shell heredocs only. Mirrors `shfmt --keep-padding`. | | `function_next_line` | Shell heredocs only. Mirrors `shfmt --func-next-line`. | | `simplify` | Shell heredocs only. Mirrors `shfmt --simplify`. | | `minify` | Shell heredocs only. Mirrors `shfmt --minify` and implies `simplify`. | Other EditorConfig properties are intentionally not interpreted by the heredoc formatter today: | Property | Reason | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `insert_final_newline` | Heredoc bodies always need a newline before the terminator, so formatted payloads always end with exactly one newline regardless of this setting. | | `end_of_line` | Heredoc payloads are embedded in the Dockerfile and should follow the parent Dockerfile's line-ending convention rather than a separate virtual target setting. | | `charset` | tally already reads Dockerfiles as text and does not transcode heredoc payloads during formatting. | | `trim_trailing_whitespace` | Formatted output for the currently supported structured formats does not emit incidental trailing whitespace. tally does not read this property or run a separate trimming pass, because whitespace may be payload data in some formats. | # Introduction Source: https://tally.wharflab.com/introduction tally is a BuildKit-native Dockerfile and Containerfile linter and formatter with safe auto-fix, LSP support, and SARIF output. tally is a production-grade **Dockerfile and Containerfile linter + formatter** built on BuildKit's official parser — the same foundation behind `docker buildx`. It catches issues before they reach production, modernizes syntax automatically, and fits cleanly into any CI pipeline or editor workflow. ```bash theme={null} # Lint everything in your repo (recursive) tally lint . # Apply all safe fixes automatically tally lint --fix Dockerfile ``` Install via Homebrew, mise, WinGet, npm, Bun, uv, pip, RubyGems, Docker, or Go. Lint your first Dockerfile in under a minute. Rules across BuildKit, tally, Hadolint, and ShellCheck namespaces. Configure rules, output formats, and fix modes with `.tally.toml`. Lint Dockerfiles through Bake targets and Compose services. ## Why tally? Dockerfile linting has traditionally meant picking a compromise: * **Hadolint** is popular and battle-tested, but it uses its own Dockerfile parser, so support for newer BuildKit features can lag behind. It focuses on reporting — not fixing. * **`docker buildx --check`** runs Docker's official BuildKit checks, but it requires the Docker/buildx toolchain and is not available if you are using Podman, Finch, or another runtime. tally takes a different approach: | Capability | tally | Hadolint | docker buildx --check | | -------------------------------------------------- | ----- | --------------- | --------------------- | | Uses BuildKit's official parser | Yes | No (own parser) | Yes | | Understands heredocs, `RUN --mount`, `COPY --link` | Yes | Partial | Yes | | Safe auto-fix (`--fix`) | Yes | No | No | | AI-powered fixes (`--fix-unsafe`) | Yes | No | No | | Bake and Compose entrypoints | Yes | No | Partial (Bake only) | | No Docker daemon required | Yes | Yes | No | | SARIF output | Yes | Yes | No | | LSP / editor integration | Yes | Limited | No | | Windows-container aware | Yes | No | Partial | | PowerShell semantic analysis | Yes | No | No | ## Key capabilities ### BuildKit-native parsing tally uses BuildKit's official parser — the same one that `docker buildx` uses. It correctly handles modern syntax like heredocs, `RUN --mount=type=cache`, `COPY --link`, and `ADD --checksum` without lagging behind new Docker features. ### Rules across four namespaces | Namespace | Source | | ------------- | ----------------------------------------------------- | | `buildkit/` | Docker's official Dockerfile checks | | `tally/` | Custom rules including secret detection with gitleaks | | `hadolint/` | Hadolint-compatible Dockerfile rules | | `shellcheck/` | Shell script analysis within `RUN` instructions | Rules are grouped by category: correctness, security, performance, style, and maintainability. See the [rules reference](/rules/overview) for the full list. ### Safe auto-fix `--fix` applies safe, mechanical rewrites — things like canonicalizing `STOPSIGNAL`, adding `--chown` flags, and converting `RUN echo` patterns to `COPY` heredocs. Nothing is applied that could change build behavior. ### Build invocation context tally can lint Dockerfiles through Docker Buildx Bake and Docker Compose entrypoints. When you run `tally lint docker-bake.hcl` or `tally lint compose.yaml`, tally resolves each target or service into the Dockerfile, build context, build args, platform, and selected target stage. See [Build invocations](/guides/build-invocations) for the full workflow. ### AI AutoFix via ACP `--fix-unsafe` unlocks opt-in AI AutoFix for improvements that are hard to express as a deterministic rewrite. Instead of requiring an API key, tally integrates with **ACP (Agent Client Protocol)** so you can use the AI agent you already trust — Gemini CLI, OpenCode, GitHub Copilot CLI, and more. All AI fixes are rule-driven (one narrow transformation at a time) and verified by re-parsing and re-linting before anything is written to disk. See the [AI AutoFix guide](/guides/ai-autofix) for details. ### LSP and editor support `tally lsp` exposes a full Language Server Protocol server over stdio, giving you real-time diagnostics in any LSP-compatible editor. Official extensions are available for [VS Code](https://marketplace.visualstudio.com/items?itemName=wharflab.tally) and [JetBrains IDEs](https://plugins.jetbrains.com/plugin/30255-tally). ### CI-ready output formats tally supports text, JSON, SARIF, GitHub Actions annotations, and Markdown output. SARIF integrates with GitHub Code Scanning, Azure DevOps, and other tools. The Markdown format is optimized for AI agents and token efficiency. ## Get started Choose your package manager: Homebrew, mise, WinGet, npm, Bun, uv, pip, RubyGems, Docker, or Go. Lint a Dockerfile, read the output, apply fixes, and set up `.tally.toml`. Use the build definition your project already uses. # Quick start Source: https://tally.wharflab.com/quickstart Lint your first Dockerfile in under a minute. Pick your preferred package manager: ```bash Homebrew theme={null} brew trust --formula wharflab/tap/tally brew install --require-sha wharflab/tap/tally ``` ```bash mise theme={null} mise use -g github:wharflab/tally@latest ``` ```bash npm theme={null} npm install -g tally-cli ``` ```bash Bun theme={null} bun add -g tally-cli ``` ```bash uv theme={null} uv tool install tally-cli ``` ```bash pip theme={null} pip install tally-cli ``` ```bash Go theme={null} go install github.com/wharflab/tally@latest ``` See [Installation](/installation) for WinGet, RubyGems, Docker, and from-source options. Run tally against a single file or an entire directory (recursive): ```bash theme={null} # Single file tally lint Dockerfile # All Dockerfiles in the current repo tally lint . ``` tally discovers files matching `Dockerfile`, `Dockerfile.*`, `*.Dockerfile`, `Containerfile`, and `Containerfile.*`. To make the same lint command available through Docker, register the Docker CLI plugin: ```bash theme={null} tally register-docker-plugin docker lint Dockerfile ``` If your build is defined by Docker Buildx Bake or Docker Compose, pass that file directly: ```bash theme={null} # Lint Bake targets from the default group tally lint docker-bake.hcl # Lint Compose services with build sections tally lint compose.yaml ``` Use `--target` for Bake targets or groups and `--service` for Compose services: ```bash theme={null} tally lint docker-bake.hcl --target api tally lint compose.yaml --service api ``` See [Build invocations](/guides/build-invocations) for context handling, output attribution, and limitations. By default tally prints human-readable output with source snippets: ```text theme={null} WARNING: StageNameCasing - https://docs.docker.com/go/dockerfile/rule/stage-name-casing/ Stage name 'Builder' should be lowercase Dockerfile:2 ──────────────────── 1 │ FROM alpine >>>2 │ FROM ubuntu AS Builder 3 │ RUN echo "hello" ──────────────────── ``` Exit code `1` means violations were found. Exit code `0` means clean. Many rules are auto-fixable. Apply all safe fixes with `--fix`: ```bash theme={null} tally lint --fix Dockerfile ``` `--fix` modifies files in place. Commit your changes before running it, or review the diff afterward. Create `.tally.toml` in your project root to set defaults for your whole team: ```toml .tally.toml theme={null} [output] format = "text" fail-level = "warning" [rules] include = ["buildkit/*", "tally/*", "hadolint/*"] ``` Config is discovered automatically — no `--config` flag needed. In direct Dockerfile mode, pass `--context` to unlock rules that check `.dockerignore` interactions: ```bash theme={null} tally lint --context . Dockerfile ``` Bake and Compose entrypoints derive the build context from the selected target or service, so they do not use `--context`. Use an inline directive to suppress a specific rule on the next line: ```dockerfile theme={null} # tally ignore=StageNameCasing FROM alpine AS Build ``` Add a reason to document why: ```dockerfile theme={null} # tally ignore=DL3007;reason=Pinning at CI level via Renovate FROM ubuntu:latest ``` ## Lint from stdin Pass `-` as the filename to read from stdin. Useful in pipelines: ```bash theme={null} cat Dockerfile | tally lint - ``` With `--fix`, the fixed content is written to stdout: ```bash theme={null} cat Dockerfile | tally lint --fix - > Dockerfile.fixed ``` ## Next steps Full reference for `.tally.toml`, environment variables, and CLI flags. Lint Dockerfiles through Bake targets and Compose services. Learn about safe and unsafe fixes, and per-rule fix modes. Browse all rules across BuildKit, tally, Hadolint, and ShellCheck namespaces. Add tally to GitHub Actions, GitLab CI, and other pipelines. # buildkit/ConsistentInstructionCasing Source: https://tally.wharflab.com/rules/buildkit/ConsistentInstructionCasing Instruction keywords should use consistent casing throughout the Dockerfile. Instruction keywords should use consistent casing throughout the Dockerfile. | Property | Value | | -------- | ------------- | | Severity | Warning | | Category | Style | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description Instruction keywords should use consistent casing (all lowercase or all uppercase). Using a case that mixes uppercase and lowercase, such as PascalCase or snakeCase, results in poor readability. ## Examples Bad: ```dockerfile theme={null} From alpine Run echo hello > /greeting.txt EntRYpOiNT ["cat", "/greeting.txt"] ``` Good (all uppercase): ```dockerfile theme={null} FROM alpine RUN echo hello > /greeting.txt ENTRYPOINT ["cat", "/greeting.txt"] ``` Good (all lowercase): ```dockerfile theme={null} from alpine run echo hello > /greeting.txt entrypoint ["cat", "/greeting.txt"] ``` ## Auto-fix The fix changes instruction keywords to match the majority casing in the Dockerfile. ```dockerfile theme={null} # Before (majority is uppercase) FROM alpine run echo hello # After (with --fix) FROM alpine RUN echo hello ``` ## Reference * [buildkit/ConsistentInstructionCasing](https://docs.docker.com/reference/build-checks/consistent-instruction-casing/) # buildkit/CopyIgnoredFile Source: https://tally.wharflab.com/rules/buildkit/CopyIgnoredFile Attempting to Copy file that is excluded by .dockerignore. Attempting to Copy file that is excluded by .dockerignore. | Property | Value | | -------- | ----------- | | Severity | Warning | | Category | Correctness | | Default | Enabled | ## Description When you use the `ADD` or `COPY` instructions in a Dockerfile, you should ensure that the files to be copied into the image do not match a pattern present in `.dockerignore`. Files which match the patterns in a `.dockerignore` file are not present in the context of the image when it is built. Trying to copy or add a file which is missing from the context will result in a build error. ## Build context requirement This rule needs build context information. In direct Dockerfile mode, pass a local context directory: ```bash theme={null} tally lint --context . Dockerfile ``` When linting a Bake or Compose entrypoint, tally uses the local `context` declared by the selected target or service: ```bash theme={null} tally lint docker-bake.hcl --target api tally lint compose.yaml --service api ``` Remote, tar, git, and empty contexts are valid build declarations, but tally does not fetch or unpack them during linting. For those contexts, this rule only reports problems that can be determined without reading context files. ## Examples Given a `.dockerignore` containing `*/tmp/*`: Bad: ```dockerfile theme={null} FROM scratch COPY ./tmp/helloworld.txt /helloworld.txt ``` Good: ```dockerfile theme={null} FROM scratch COPY ./forever/helloworld.txt /helloworld.txt ``` ## Reference * [buildkit/CopyIgnoredFile](https://docs.docker.com/reference/build-checks/copy-ignored-file/) # buildkit/DuplicateStageName Source: https://tally.wharflab.com/rules/buildkit/DuplicateStageName Stage names in a multi-stage build should be unique. Stage names in a multi-stage build should be unique. | Property | Value | | -------- | ----------- | | Severity | Error | | Category | Correctness | | Default | Enabled | ## Description Defining multiple stages with the same name results in an error because the builder is unable to uniquely resolve the stage name reference. ## Examples Bad: ```dockerfile theme={null} FROM debian:latest AS builder RUN apt-get update; apt-get install -y curl FROM golang:latest AS builder ``` Good: ```dockerfile theme={null} FROM debian:latest AS deb-builder RUN apt-get update; apt-get install -y curl FROM golang:latest AS go-builder ``` ## Supersedes * [hadolint/DL3024](../hadolint/DL3024) ## Reference * [buildkit/DuplicateStageName](https://docs.docker.com/reference/build-checks/duplicate-stage-name/) # buildkit/ExposeInvalidFormat Source: https://tally.wharflab.com/rules/buildkit/ExposeInvalidFormat EXPOSE instruction should not define an IP address or host-port mapping. EXPOSE instruction should not define an IP address or host-port mapping. | Property | Value | | -------- | ----------- | | Severity | Warning | | Category | Correctness | | Default | Enabled | ## Description The `EXPOSE` instruction in a Dockerfile is used to indicate which ports the container listens on at runtime. It should not include an IP address or host-port mapping. Including an IP address or host-port mapping in the `EXPOSE` instruction does not actually publish the port and can be misleading. Use `docker run -p` or `docker compose` port mappings to bind host ports at runtime. ## Examples Bad: ```dockerfile theme={null} FROM alpine EXPOSE 127.0.0.1:80:80 ``` Good: ```dockerfile theme={null} FROM alpine EXPOSE 80 ``` Bad: ```dockerfile theme={null} FROM alpine EXPOSE 80:80 ``` Good: ```dockerfile theme={null} FROM alpine EXPOSE 80 ``` ## Reference * [buildkit/ExposeInvalidFormat](https://docs.docker.com/reference/build-checks/expose-invalid-format/) # buildkit/ExposeProtoCasing Source: https://tally.wharflab.com/rules/buildkit/ExposeProtoCasing Protocol names in `EXPOSE` instructions should be lowercase. Protocol names in `EXPOSE` instructions should be lowercase. | Property | Value | | -------- | ------------- | | Severity | Warning | | Category | Style | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description Protocol names in the `EXPOSE` instruction should be specified in lowercase to maintain consistency and readability. ## Examples Bad: ```dockerfile theme={null} EXPOSE 80/TcP ``` Good: ```dockerfile theme={null} EXPOSE 80/tcp ``` ## Auto-fix The fix lowercases the protocol in `EXPOSE` port specs. ```dockerfile theme={null} # Before EXPOSE 8080/TCP # After (with --fix) EXPOSE 8080/tcp ``` ## Reference * [buildkit/ExposeProtoCasing](https://docs.docker.com/reference/build-checks/expose-proto-casing/) # buildkit/FromAsCasing Source: https://tally.wharflab.com/rules/buildkit/FromAsCasing The `AS` and `FROM` keywords' casing should match. The `AS` and `FROM` keywords' casing should match. | Property | Value | | -------- | ------------- | | Severity | Warning | | Category | Style | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description While Dockerfile keywords can be either uppercase or lowercase, mixing case styles is not recommended for readability. This rule reports violations where mixed case style occurs for a `FROM` instruction with an `AS` keyword declaring a stage name. ## Examples Bad: ```dockerfile theme={null} FROM debian:latest as builder ``` `FROM` is uppercase but `as` is lowercase. Good: ```dockerfile theme={null} FROM debian:latest AS deb-builder ``` ```dockerfile theme={null} from debian:latest as deb-builder ``` Both keywords use the same casing. ## Auto-fix The fix changes the `AS` keyword casing to match the `FROM` keyword. ```dockerfile theme={null} # Before FROM alpine as builder # After (with --fix) FROM alpine AS builder ``` ## Related * [buildkit/ConsistentInstructionCasing](./ConsistentInstructionCasing) ## Reference * [buildkit/FromAsCasing](https://docs.docker.com/reference/build-checks/from-as-casing/) # buildkit/FromPlatformFlagConstDisallowed Source: https://tally.wharflab.com/rules/buildkit/FromPlatformFlagConstDisallowed FROM `--platform` flag should not use a constant value. FROM `--platform` flag should not use a constant value. | Property | Value | | -------- | -------------------------------------------------------------------------------- | | Severity | Off | | Category | Best Practice | | Default | Disabled (superseded by [`tally/platform-mismatch`](../tally/platform-mismatch)) | ## Tally behavior deviation Tally disables this rule by default because hardcoding `--platform` is legitimate in several real-world scenarios: * **ARM-only services.** Deployments targeting AWS Graviton or other ARM-only infrastructure use `FROM --platform=linux/arm64` to ensure the correct architecture regardless of where the build runs. * **Windows containers.** Windows Dockerfiles use `FROM --platform=windows/amd64 mcr.microsoft.com/...` to explicitly target Windows, which is necessary when the builder could be multi-platform. * **Cross-compilation.** Go and Rust projects commonly use `FROM --platform=linux/amd64 golang:1.22` for a specific builder stage while the final image targets a different architecture. Rather than discouraging constant `--platform` values, tally validates them against the registry with [`tally/platform-mismatch`](../tally/platform-mismatch). This catches provable errors (image doesn't publish the requested platform) without flagging intentional platform pinning. You can re-enable this rule via configuration if you prefer the BuildKit behavior: ```yaml theme={null} rules: buildkit/FromPlatformFlagConstDisallowed: severity: warning ``` ## Description When the `--platform` flag appears with a hardcoded value, it restricts image building to a single target platform, preventing multi-platform images. The recommended strategy involves: * Removing `FROM --platform` and applying `--platform` at build time. * Substituting `$BUILDPLATFORM` or comparable variable expressions. * Naming stages to reflect platform when containing platform-specific operations. ## Examples Bad: ```dockerfile theme={null} FROM --platform=linux/amd64 alpine AS base RUN apk add --no-cache git ``` Good (default platform): ```dockerfile theme={null} FROM alpine AS base RUN apk add --no-cache git ``` Good (meta variable): ```dockerfile theme={null} FROM --platform=${BUILDPLATFORM} alpine AS base RUN apk add --no-cache git ``` Good (multi-stage build with target architecture): ```dockerfile theme={null} FROM --platform=linux/amd64 alpine AS build_amd64 ... FROM --platform=linux/arm64 alpine AS build_arm64 ... FROM build_${TARGETARCH} AS build ... ``` ## Supersedes * [hadolint/DL3029](../hadolint/DL3029) ## See also * [`tally/platform-mismatch`](../tally/platform-mismatch) — validates explicit `--platform` against the registry instead of discouraging it * [buildkit/FromPlatformFlagConstDisallowed](https://docs.docker.com/reference/build-checks/from-platform-flag-const-disallowed/) # buildkit/InvalidBaseImagePlatform Source: https://tally.wharflab.com/rules/buildkit/InvalidBaseImagePlatform Validates that the platform of an external base image matches the expected target platform. Validates that the platform of an external base image matches the expected target platform. | Property | Value | | -------- | -------------------------------------------------------------------------------- | | Severity | Off | | Category | Correctness | | Default | Disabled (superseded by [`tally/platform-mismatch`](../tally/platform-mismatch)) | ## Tally behavior deviation Tally disables this rule by default because its host-dependent design is fundamentally broken for a static linter: * **Non-deterministic results across machines.** The rule compares resolved image platforms against the host's default platform (via `runtime.GOARCH`). The same Dockerfile produces different violations on `linux/amd64` CI vs `macOS/arm64` developer laptops. * **False positives on Windows containers.** The expected OS is hardcoded to `"linux"`, so any Windows base image (e.g., `mcr.microsoft.com/windows/servercore`) is always flagged as a mismatch. * **Fires without explicit intent.** When no `--platform` flag is set on `FROM`, the rule still compares against the host — even though the builder will pick the correct platform at build time. Use [`tally/platform-mismatch`](../tally/platform-mismatch) instead. That rule only fires when `--platform` is explicitly set on `FROM` and the registry does not provide the requested platform, producing deterministic results regardless of host. You can re-enable this rule via configuration if you prefer the BuildKit behavior: ```yaml theme={null} rules: buildkit/InvalidBaseImagePlatform: severity: error ``` ## Description When using `--platform` or `$TARGETPLATFORM`, this rule checks that the base image actually supports the requested platform by resolving image metadata from the registry. This is an async rule that runs with `--slow-checks`, as it requires resolving image metadata from the registry. ## Examples Bad (image not available for requested platform): ```dockerfile theme={null} FROM --platform=linux/s390x ubuntu:22.04 # Error if ubuntu:22.04 is not available for linux/s390x ``` Good: ```dockerfile theme={null} FROM --platform=linux/amd64 ubuntu:22.04 ``` The error message includes available platforms: ```text theme={null} Base image ubuntu:22.04 was pulled with platform "linux/arm64", expected "linux/s390x" for current build ``` ## See also * [`tally/platform-mismatch`](../tally/platform-mismatch) — deterministic replacement that only validates explicit `--platform` flags # buildkit/InvalidDefaultArgInFrom Source: https://tally.wharflab.com/rules/buildkit/InvalidDefaultArgInFrom Using the global ARGs with default values should produce a valid build. Using the global ARGs with default values should produce a valid build. | Property | Value | | -------- | ----------- | | Severity | Error | | Category | Correctness | | Default | Enabled | ## Description An `ARG` used in an image reference should be valid when no build arguments are used. An image build should not require `--build-arg` to produce a valid build. If a global `ARG` has no default value and is interpolated into a `FROM` instruction, the resulting image reference may be invalid when the argument is not supplied at build time. ## Examples Bad: ```dockerfile theme={null} ARG TAG FROM busybox:${TAG} ``` Good: ```dockerfile theme={null} ARG TAG=latest FROM busybox:${TAG} ``` Good (empty ARG is OK if image is valid with it empty): ```dockerfile theme={null} ARG VARIANT FROM busybox:stable${VARIANT} ``` Good (default value syntax): ```dockerfile theme={null} ARG TAG FROM alpine:${TAG:-3.14} ``` ## Reference * [https://docs.docker.com/reference/build-checks/invalid-default-arg-in-from/](https://docs.docker.com/reference/build-checks/invalid-default-arg-in-from/) # buildkit/InvalidDefinitionDescription Source: https://tally.wharflab.com/rules/buildkit/InvalidDefinitionDescription Comments for build stages or arguments should follow the description format. Comments for build stages or arguments should follow the description format. | Property | Value | | -------- | ------------------ | | Severity | Info | | Category | Style | | Default | Off (experimental) | | Auto-fix | Yes (`--fix`) | ## Description Comments for build stages or arguments should follow the format: `# `. If a comment is not intended to be a description, add an empty line or comment between the instruction and the comment. The `--call=outline` and `--call=targets` flags for the `docker build` command print descriptions for build targets and arguments. The descriptions are generated from Dockerfile comments that immediately precede the `FROM` or `ARG` instruction and that begin with the name of the build stage or argument. ## Examples Bad: ```dockerfile theme={null} # a non-descriptive comment FROM scratch AS base # another non-descriptive comment ARG VERSION=1 ``` Good (empty line separating): ```dockerfile theme={null} # a non-descriptive comment FROM scratch AS base # another non-descriptive comment ARG VERSION=1 ``` Good (proper description format): ```dockerfile theme={null} # base is a stage for compiling source FROM scratch AS base # VERSION This is the version number. ARG VERSION=1 ``` ## Auto-fix The fix inserts an empty line between the non-description comment and the instruction. ```dockerfile theme={null} # Before # Some comment FROM alpine AS builder # After (with --fix) # Some comment FROM alpine AS builder ``` ## Reference * [buildkit/InvalidDefinitionDescription](https://docs.docker.com/reference/build-checks/invalid-definition-description/) # buildkit/JSONArgsRecommended Source: https://tally.wharflab.com/rules/buildkit/JSONArgsRecommended JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals. JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals. | Property | Value | | -------- | ------------- | | Severity | Info | | Category | Best Practice | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description `ENTRYPOINT` and `CMD` instructions both support shell form and exec form. When you use shell form, the executable runs as a child process to a shell, which doesn't pass signals. This means that the program running in the container can't detect OS signals like `SIGTERM` and `SIGKILL` and respond to them correctly. ## Examples Bad: ```dockerfile theme={null} FROM alpine ENTRYPOINT my-program start # entrypoint becomes: /bin/sh -c my-program start ``` Good: ```dockerfile theme={null} FROM alpine ENTRYPOINT ["my-program", "start"] # entrypoint becomes: my-program start ``` ### Workarounds If you need shell features (variable expansion, piping, command chaining), you can: 1. Create a wrapper script: ```dockerfile theme={null} FROM alpine RUN apk add bash COPY --chmod=755 < # After (with --fix) LABEL org.opencontainers.image.authors="John Doe " ``` ## Supersedes * [hadolint/DL4000](../hadolint/DL4000) ## Reference * [buildkit/MaintainerDeprecated](https://docs.docker.com/reference/build-checks/maintainer-deprecated/) # buildkit/MultipleInstructionsDisallowed Source: https://tally.wharflab.com/rules/buildkit/MultipleInstructionsDisallowed Multiple CMD instructions should not be used in the same stage because only the last one will be used. Multiple CMD instructions should not be used in the same stage because only the last one will be used. | Property | Value | | -------- | ------------- | | Severity | Warning | | Category | Best Practice | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description If you have multiple `CMD`, `HEALTHCHECK`, or `ENTRYPOINT` instructions in your Dockerfile, only the last occurrence is used. An image can only ever have one `CMD`, one `HEALTHCHECK`, and one `ENTRYPOINT`. ## Examples Bad: ```dockerfile theme={null} FROM alpine ENTRYPOINT ["echo", "Hello, Norway!"] ENTRYPOINT ["echo", "Hello, Sweden!"] # Only "Hello, Sweden!" will be printed ``` Good: ```dockerfile theme={null} FROM alpine ENTRYPOINT ["echo", "Hello, Norway!\nHello, Sweden!"] ``` You can have both a regular `CMD` and a separate `CMD` for `HEALTHCHECK`: ```dockerfile theme={null} FROM python:alpine RUN apk add curl HEALTHCHECK --interval=1s --timeout=3s \ CMD ["curl", "-f", "http://localhost:8080"] CMD ["python", "-m", "http.server", "8080"] ``` ## Auto-fix The fix comments out duplicate `CMD`/`ENTRYPOINT`/`HEALTHCHECK` instructions, keeping only the last one in each stage. ```dockerfile theme={null} # Before CMD echo "first" CMD echo "second" # After (with --fix) # [commented out by tally - Docker will ignore all but last CMD]: CMD echo "first" CMD echo "second" ``` ## Supersedes * [hadolint/DL3012](../hadolint/DL3012) (multiple `HEALTHCHECK`) * [hadolint/DL4003](../hadolint/DL4003) (multiple `CMD`) * [hadolint/DL4004](../hadolint/DL4004) (multiple `ENTRYPOINT`) ## Reference * [buildkit/MultipleInstructionsDisallowed](https://docs.docker.com/reference/build-checks/multiple-instructions-disallowed/) # buildkit/NoEmptyContinuation Source: https://tally.wharflab.com/rules/buildkit/NoEmptyContinuation Empty continuation lines are deprecated and will cause errors in future Dockerfile syntax versions. Empty continuation lines are deprecated and will cause errors in future Dockerfile syntax versions. | Property | Value | | -------- | ------------- | | Severity | Error | | Category | Correctness | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description Support for empty continuation (`\`) lines has been deprecated and will generate errors in future versions of the Dockerfile syntax. Empty continuation lines are empty lines following a newline escape. ## Examples Bad: ```dockerfile theme={null} FROM alpine EXPOSE \ 80 ``` Good: ```dockerfile theme={null} FROM alpine EXPOSE \ # Port 80 ``` Bad: ```dockerfile theme={null} FROM alpine RUN apk add \ gnupg \ curl ``` Good (empty lines removed): ```dockerfile theme={null} FROM alpine RUN apk add \ gnupg \ curl ``` ## Auto-fix The fix removes empty continuation lines from multi-line commands. ## Reference * [buildkit/NoEmptyContinuation](https://docs.docker.com/reference/build-checks/no-empty-continuation/) # buildkit/RedundantTargetPlatform Source: https://tally.wharflab.com/rules/buildkit/RedundantTargetPlatform Setting platform to predefined `$TARGETPLATFORM` in FROM is redundant as this is the default behavior. Setting platform to predefined `$TARGETPLATFORM` in FROM is redundant as this is the default behavior. | Property | Value | | -------- | ------------- | | Severity | Info | | Category | Best Practice | | Default | Enabled | ## Description A custom platform can be used for a base image. The default platform is the same platform as the target output, so setting the platform to `$TARGETPLATFORM` is redundant and unnecessary. ## Examples Bad: ```dockerfile theme={null} FROM --platform=$TARGETPLATFORM alpine AS builder RUN apk add --no-cache git ``` Good: ```dockerfile theme={null} FROM alpine AS builder RUN apk add --no-cache git ``` ## Reference * [buildkit/RedundantTargetPlatform](https://docs.docker.com/reference/build-checks/redundant-target-platform/) # buildkit/ReservedStageName Source: https://tally.wharflab.com/rules/buildkit/ReservedStageName `scratch` is reserved and should not be used as a stage name. `scratch` is reserved and should not be used as a stage name. | Property | Value | | -------- | ----------- | | Severity | Error | | Category | Correctness | | Default | Enabled | ## Description Reserved words should not be used as names for stages in multi-stage builds. The reserved words are: `context`, `scratch`. Using a reserved word as a stage name can conflict with built-in BuildKit behavior and produce confusing build errors. ## Examples Bad: ```dockerfile theme={null} FROM alpine AS scratch FROM alpine AS context ``` Good: ```dockerfile theme={null} FROM alpine AS builder ``` ## Reference * [buildkit/ReservedStageName](https://docs.docker.com/reference/build-checks/reserved-stage-name/) # buildkit/SecretsUsedInArgOrEnv Source: https://tally.wharflab.com/rules/buildkit/SecretsUsedInArgOrEnv Potentially sensitive data should not be used in the ARG or ENV commands. Potentially sensitive data should not be used in the ARG or ENV commands. | Property | Value | | -------- | -------- | | Severity | Warning | | Category | Security | | Default | Enabled | ## Description While it is common to pass secrets to running processes through environment variables during local development, setting secrets in a Dockerfile using `ENV` or `ARG` is insecure because they persist in the final image. This rule reports violations where `ENV` and `ARG` keys indicate that they contain sensitive data. Instead of `ARG` or `ENV`, you should use secret mounts, which expose secrets to your builds in a secure manner and do not persist in the final image or its metadata. ## Examples Bad: ```dockerfile theme={null} FROM scratch ARG AWS_SECRET_ACCESS_KEY ``` Good (use secret mounts instead): ```dockerfile theme={null} FROM scratch RUN --mount=type=secret,id=aws_key \ AWS_SECRET_ACCESS_KEY=$(cat /run/secrets/aws_key) \ aws s3 cp ... ``` See also: [tally/secrets-in-code](../tally/secrets-in-code) complements this rule by detecting actual secret *values* (not just variable names). ## Reference * [buildkit/SecretsUsedInArgOrEnv](https://docs.docker.com/reference/build-checks/secrets-used-in-arg-or-env/) * [Build secrets](https://docs.docker.com/build/building/secrets/) # buildkit/StageNameCasing Source: https://tally.wharflab.com/rules/buildkit/StageNameCasing Stage names in multi-stage builds should be lowercase. Stage names in multi-stage builds should be lowercase. | Property | Value | | -------- | ------------- | | Severity | Warning | | Category | Style | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description Stage name should be lowercase. To help distinguish Dockerfile instruction keywords from identifiers, this rule forces names of stages in a multi-stage Dockerfile to be all lowercase. ## Examples Bad: ```dockerfile theme={null} FROM alpine AS BuilderBase ``` Good: ```dockerfile theme={null} FROM alpine AS builder-base ``` ## Auto-fix The fix renames the stage to lowercase and updates all references (`FROM`, `COPY --from`). ```dockerfile theme={null} # Before FROM alpine AS Builder COPY --from=Builder /app . # After (with --fix) FROM alpine AS builder COPY --from=builder /app . ``` ## Reference * [buildkit/StageNameCasing](https://docs.docker.com/reference/build-checks/stage-name-casing/) # buildkit/UndefinedArgInFrom Source: https://tally.wharflab.com/rules/buildkit/UndefinedArgInFrom FROM argument is not declared. FROM argument is not declared. | Property | Value | | -------- | ----------- | | Severity | Warning | | Category | Correctness | | Default | Enabled | ## Description This rule warns for cases where you are consuming an undefined build argument in `FROM` instructions. Interpolating build arguments in `FROM` instructions can be a good way to add flexibility to your build. However, if the argument is never declared with `ARG`, the variable silently resolves to an empty string, which is almost certainly not the intended behavior. This check also tries to detect and warn when a `FROM` instruction references misspelled built-in build arguments, like `BUILDPLATFORM`. ## Examples Bad: ```dockerfile theme={null} FROM node:22${VARIANT} AS jsbuilder ``` Good: ```dockerfile theme={null} ARG VARIANT="-alpine3.20" FROM node:22${VARIANT} AS jsbuilder ``` You can also pass the argument at build time: ```console theme={null} docker buildx build --build-arg ALPINE_VERSION=edge . ``` ## Reference * [buildkit/UndefinedArgInFrom](https://docs.docker.com/reference/build-checks/undefined-arg-in-from/) # buildkit/UndefinedVar Source: https://tally.wharflab.com/rules/buildkit/UndefinedVar Usage of undefined variable. Usage of undefined variable. | Property | Value | | -------- | ----------- | | Severity | Warning | | Category | Correctness | | Default | Enabled | ## Description This check ensures that environment variables and build arguments are correctly declared before being used. While undeclared variables might not cause an immediate build failure, they can lead to unexpected behavior. It also detects common mistakes like typos in variable names. This check does not evaluate undefined variables for `RUN`, `CMD`, and `ENTRYPOINT` instructions where you use the shell form, because when you use shell form, variables are resolved by the command shell. ## Examples Bad: ```dockerfile theme={null} FROM alpine AS base COPY $foo . ``` Good: ```dockerfile theme={null} FROM alpine AS base ARG foo COPY $foo . ``` Bad (typo detection): ```dockerfile theme={null} FROM alpine ENV PATH=$PAHT:/app/bin ``` Output: `Usage of undefined variable '$PAHT' (did you mean $PATH?)` ## Supersedes * [hadolint/DL3044](../hadolint/DL3044) ## Reference * [buildkit/UndefinedVar](https://docs.docker.com/reference/build-checks/undefined-var/) # buildkit/WorkdirRelativePath Source: https://tally.wharflab.com/rules/buildkit/WorkdirRelativePath Relative workdir can have unexpected results if the base image changes. Relative workdir can have unexpected results if the base image changes. | Property | Value | | -------- | ---------------------------------------------------------- | | Severity | Warning | | Category | Best Practice | | Default | Enabled | | Auto-fix | Yes (suggestion `--fix-unsafe`; safe with `--slow-checks`) | ## Description When specifying `WORKDIR` in a build stage, you can use an absolute path, like `/build`, or a relative path, like `./build`. Using a relative path means that the working directory is relative to whatever the previous working directory was. This rule warns if you use `WORKDIR` with a relative path without first specifying an absolute path in the same Dockerfile. The rationale is that using a relative working directory for a base image built externally is prone to breaking, since the working directory may change upstream without warning. ## Examples Bad (assumes `WORKDIR` in base image is `/`): ```dockerfile theme={null} FROM nginx AS web WORKDIR usr/share/nginx/html COPY public . ``` Good: ```dockerfile theme={null} FROM nginx AS web WORKDIR /usr/share/nginx/html COPY public . ``` ## Auto-fix Replaces the relative `WORKDIR` with an absolute path. * **Fast path** (no registry access): resolves against `/` as a default (`FixSuggestion`, requires `--fix-unsafe`). * **With `--slow-checks`**: resolves against the base image's actual `WORKDIR` from the registry (`FixSafe`, applied with `--fix`). Chained relative WORKDIRs are resolved cumulatively. ```dockerfile theme={null} # Before FROM nginx AS web WORKDIR usr/share/nginx/html # After (with --fix-unsafe, fast path — assumes base WORKDIR is /) FROM nginx AS web WORKDIR /usr/share/nginx/html # After (with --fix --slow-checks, base image has WORKDIR /etc/nginx) FROM nginx AS web WORKDIR /etc/nginx/usr/share/nginx/html ``` ## Supersedes * [hadolint/DL3000](../hadolint/DL3000) ## Reference * [buildkit/WorkdirRelativePath](https://docs.docker.com/reference/build-checks/workdir-relative-path/) # BuildKit rules Source: https://tally.wharflab.com/rules/buildkit/overview tally supports all 22 BuildKit checks, including reimplementations of build-time checks as pure static analysis. tally supports **22/22** BuildKit checks. 5 are captured directly from BuildKit's parser; 17 are reimplemented as static rules so they work without running Docker/BuildKit. BuildKit checks come from Docker's official [build checks reference](https://docs.docker.com/reference/build-checks/). tally integrates them in two ways: * **Implemented by tally** — BuildKit normally runs these during LLB conversion (i.e., when actually building). tally reimplements them as pure static checks so they catch issues without a Docker daemon. * **Captured from BuildKit parser** — These are emitted by BuildKit during Dockerfile parsing and forwarded directly to tally's diagnostic pipeline. All 11 auto-fixable rules are marked with 🔧. Run `tally lint --fix` to apply safe fixes automatically. *** ## Implemented by tally These 17 checks correspond to BuildKit's LLB-conversion checks. tally runs them statically, so you get full coverage without Docker or BuildKit installed. All commands within the Dockerfile should use the same casing (either upper or lower). **Severity:** Warning (enabled by default) · **Auto-fixable:** Yes **Docker docs:** [consistent-instruction-casing](https://docs.docker.com/reference/build-checks/consistent-instruction-casing/) ```dockerfile theme={null} # Violation: mixed casing FROM alpine run apk add curl COPY . /app cmd ["./app"] ``` Attempting to COPY a file that is excluded by `.dockerignore`. **Severity:** Warning (enabled by default) · **Auto-fixable:** No **Docker docs:** [copy-ignored-file](https://docs.docker.com/reference/build-checks/copy-ignored-file/) ```dockerfile theme={null} # .dockerignore contains: *.log # Violation: copying a file excluded by .dockerignore COPY app.log /app/app.log ``` Stage names should be unique. **Severity:** Error (enabled by default) · **Auto-fixable:** No **Docker docs:** [duplicate-stage-name](https://docs.docker.com/reference/build-checks/duplicate-stage-name/) ```dockerfile theme={null} # Violation: two stages share the name "builder" FROM alpine AS builder RUN echo "first" FROM ubuntu AS builder RUN echo "second" ``` IP address and host-port mapping should not be used in EXPOSE. This will become an error in a future release. **Severity:** Warning (enabled by default) · **Auto-fixable:** No **Docker docs:** [expose-invalid-format](https://docs.docker.com/reference/build-checks/expose-invalid-format/) ```dockerfile theme={null} # Violation: host-port mapping in EXPOSE EXPOSE 0.0.0.0:8080 ``` Protocol in EXPOSE instruction should be lowercase. **Severity:** Warning (enabled by default) · **Auto-fixable:** Yes **Docker docs:** [expose-proto-casing](https://docs.docker.com/reference/build-checks/expose-proto-casing/) ```dockerfile theme={null} # Violation: protocol in uppercase EXPOSE 80/TCP EXPOSE 443/UDP ``` `FROM --platform` flag should not use a constant value. **Severity:** Off by default · **Auto-fixable:** No **Docker docs:** [from-platform-flag-const-disallowed](https://docs.docker.com/reference/build-checks/from-platform-flag-const-disallowed/) ```dockerfile theme={null} # Violation: hardcoded platform instead of using build arg FROM --platform=linux/amd64 alpine ``` Base image platform does not match expected target platform. **Severity:** Off by default · **Auto-fixable:** No ```dockerfile theme={null} # Violation: explicit platform conflicts with the target platform FROM --platform=linux/arm64 node:20 AS builder ``` Default value for a global ARG results in an empty or invalid base image name. **Severity:** Error (enabled by default) · **Auto-fixable:** No **Docker docs:** [invalid-default-arg-in-from](https://docs.docker.com/reference/build-checks/invalid-default-arg-in-from/) ```dockerfile theme={null} # Violation: ARG default is empty, making the image name invalid ARG BASE_IMAGE="" FROM ${BASE_IMAGE} ``` JSON arguments are recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals. **Severity:** Info (enabled by default) · **Auto-fixable:** Yes **Docker docs:** [json-args-recommended](https://docs.docker.com/reference/build-checks/json-args-recommended/) ```dockerfile theme={null} # Violation: shell form — PID 1 is /bin/sh, not the process ENTRYPOINT myapp --config /etc/myapp.conf CMD myapp ``` Legacy key/value format with whitespace separator should not be used. **Severity:** Warning (enabled by default) · **Auto-fixable:** Yes **Docker docs:** [legacy-key-value-format](https://docs.docker.com/reference/build-checks/legacy-key-value-format/) ```dockerfile theme={null} # Violation: legacy whitespace-separated ENV and LABEL syntax ENV MY_VAR my_value LABEL maintainer John Doe ``` Multiple instructions of the same type should not be used in the same stage. **Severity:** Warning (enabled by default) · **Auto-fixable:** Yes **Docker docs:** [multiple-instructions-disallowed](https://docs.docker.com/reference/build-checks/multiple-instructions-disallowed/) ```dockerfile theme={null} FROM alpine # Violation: two CMD instructions; only the last takes effect CMD ["echo", "first"] CMD ["echo", "second"] ``` Setting `--platform` to the predefined `$TARGETPLATFORM` in FROM is redundant as it is the default behavior. **Severity:** Warning (enabled by default) · **Auto-fixable:** No **Docker docs:** [redundant-target-platform](https://docs.docker.com/reference/build-checks/redundant-target-platform/) ```dockerfile theme={null} # Violation: $TARGETPLATFORM is already the default FROM --platform=$TARGETPLATFORM alpine ``` Reserved words should not be used as stage names. **Severity:** Error (enabled by default) · **Auto-fixable:** No **Docker docs:** [reserved-stage-name](https://docs.docker.com/reference/build-checks/reserved-stage-name/) ```dockerfile theme={null} # Violation: "scratch" is a reserved stage name FROM alpine AS scratch RUN echo "building" ``` Sensitive data should not be used in the ARG or ENV commands. **Severity:** Warning (enabled by default) · **Auto-fixable:** No **Docker docs:** [secrets-used-in-arg-or-env](https://docs.docker.com/reference/build-checks/secrets-used-in-arg-or-env/) ```dockerfile theme={null} # Violation: secret value baked into the image layer ARG AWS_SECRET_ACCESS_KEY ENV DATABASE_PASSWORD=supersecret ``` FROM command must use declared ARGs. **Severity:** Warning (enabled by default) · **Auto-fixable:** No **Docker docs:** [undefined-arg-in-from](https://docs.docker.com/reference/build-checks/undefined-arg-in-from/) ```dockerfile theme={null} # Violation: IMAGE_TAG is not declared before use in FROM FROM alpine:${IMAGE_TAG} ``` Variables should be defined before their use. **Severity:** Warning (enabled by default) · **Auto-fixable:** No **Docker docs:** [undefined-var](https://docs.docker.com/reference/build-checks/undefined-var/) ```dockerfile theme={null} FROM alpine # Violation: $APP_DIR was never defined with ARG or ENV COPY . $APP_DIR ``` Relative WORKDIR without a prior absolute WORKDIR can have unexpected results if the base image changes. **Severity:** Warning (enabled by default) · **Auto-fixable:** Yes **Docker docs:** [workdir-relative-path](https://docs.docker.com/reference/build-checks/workdir-relative-path/) ```dockerfile theme={null} FROM alpine # Violation: relative path depends on the base image's CWD WORKDIR app ``` *** ## Captured from BuildKit parser These 5 checks are emitted by BuildKit during Dockerfile parsing. tally captures them directly and includes them in its diagnostic output alongside statically implemented rules. The `as` keyword should match the case of the `from` keyword. **Severity:** Warning (enabled by default) · **Auto-fixable:** Yes **Docker docs:** [from-as-casing](https://docs.docker.com/reference/build-checks/from-as-casing/) ```dockerfile theme={null} # Violation: FROM is uppercase but AS is lowercase (or vice versa) FROM alpine as builder # or from alpine AS builder ``` Comments for build stages or arguments should follow the format: `# `. If this is not intended to be a description comment, add an empty line or comment between the instruction and the comment. **Severity:** Warning · **Default:** Off (experimental) · **Auto-fixable:** Yes **Docker docs:** [invalid-definition-description](https://docs.docker.com/reference/build-checks/invalid-definition-description/) ```dockerfile theme={null} # Violation: comment does not match the stage name immediately below # This is the production builder FROM alpine AS builder ``` The MAINTAINER instruction is deprecated; use a label instead to define an image author. **Severity:** Warning (enabled by default) · **Auto-fixable:** Yes **Docker docs:** [maintainer-deprecated](https://docs.docker.com/reference/build-checks/maintainer-deprecated/) ```dockerfile theme={null} FROM alpine # Violation: MAINTAINER is deprecated MAINTAINER Jane Doe ``` Empty continuation lines will become errors in a future release. **Severity:** Warning (enabled by default) · **Auto-fixable:** Yes **Docker docs:** [no-empty-continuation](https://docs.docker.com/reference/build-checks/no-empty-continuation/) ```dockerfile theme={null} FROM alpine # Violation: trailing backslash on an otherwise empty continuation line RUN echo "hello" \ \ && echo "world" ``` Stage names should be lowercase. **Severity:** Warning (enabled by default) · **Auto-fixable:** Yes **Docker docs:** [stage-name-casing](https://docs.docker.com/reference/build-checks/stage-name-casing/) ```dockerfile theme={null} # Violation: stage name uses mixed or uppercase letters FROM alpine AS Builder FROM ubuntu AS PROD ``` # hadolint/DL3000 Source: https://tally.wharflab.com/rules/hadolint/DL3000 Use absolute WORKDIR. Use absolute WORKDIR. > **Superseded by [`buildkit/WorkdirRelativePath`](../buildkit/WorkdirRelativePath)**, which provides the same check with improved diagnostics. ## Description Using a relative `WORKDIR` without first setting an absolute path can lead to unexpected results if the base image changes its working directory. ## Examples ### Problematic code ```dockerfile theme={null} FROM nginx WORKDIR usr/share/nginx/html ``` ### Correct code ```dockerfile theme={null} FROM nginx WORKDIR /usr/share/nginx/html ``` ## Reference * [hadolint/DL3000](https://github.com/hadolint/hadolint/wiki/DL3000) # hadolint/DL3001 Source: https://tally.wharflab.com/rules/hadolint/DL3001 Command does not make sense in a container. Command does not make sense in a container. | Property | Value | | -------- | ------------- | | Severity | Info | | Category | Best Practice | | Default | Enabled | ## Description For some POSIX commands it makes no sense to run them in a Docker container because they are bound to the host or are otherwise dangerous (like `shutdown`, `service`, `ps`, `free`, `top`, `kill`, `mount`, `ifconfig`). Interactive utilities also don't make much sense (`nano`, `vim`). ## Examples ### Problematic code ```dockerfile theme={null} FROM busybox RUN top ``` ### Correct code ```dockerfile theme={null} FROM busybox ``` ## Reference * [hadolint/DL3001](https://github.com/hadolint/hadolint/wiki/DL3001) # hadolint/DL3002 Source: https://tally.wharflab.com/rules/hadolint/DL3002 Last user should not be root. Last user should not be root. | Property | Value | | -------- | -------- | | Severity | Warning | | Category | Security | | Default | Enabled | ## Description Switching to the root `USER` opens up certain security risks if an attacker gets access to the container. In order to mitigate this, switch back to a non-privileged user after running the commands you need as root. ## Examples ### Problematic code ```dockerfile theme={null} FROM busybox USER root RUN ... ``` ### Correct code ```dockerfile theme={null} FROM busybox USER root RUN ... USER guest ``` ## Reference * [hadolint/DL3002](https://github.com/hadolint/hadolint/wiki/DL3002) # hadolint/DL3003 Source: https://tally.wharflab.com/rules/hadolint/DL3003 Use WORKDIR to switch to a directory. Use WORKDIR to switch to a directory. | Property | Value | | -------- | ------------- | | Severity | Warning | | Category | Best Practice | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description Only use `cd` in a subshell. Most commands can work with absolute paths. Docker provides the `WORKDIR` instruction if you really need to change the current working directory. When executed in a subshell, `cd` only affects the single `RUN` instruction, not any subsequent instructions. This can be an advantage over `WORKDIR` which affects all subsequent instructions. ## Examples ### Problematic code ```dockerfile theme={null} FROM busybox RUN cd /usr/src/app && git clone git@github.com:lukasmartinelli/hadolint.git ``` ### Correct code (with WORKDIR) ```dockerfile theme={null} FROM busybox WORKDIR /usr/src/app RUN git clone git@github.com:lukasmartinelli/hadolint.git ``` ### Correct code (with absolute paths) ```dockerfile theme={null} FROM busybox RUN cp somedir/somefile /usr/src/app/someDirInUsrSrcApp/ ``` ## Auto-fix Splits `RUN` with `cd` into `WORKDIR` + `RUN` instructions. Removes redundant `mkdir` commands before `cd` targets. ```dockerfile theme={null} # Before RUN cd /tmp && git clone ... && cd repo && make # After (with --fix) WORKDIR /tmp RUN git clone ... WORKDIR repo RUN make ``` ## Reference * [hadolint/DL3003](https://github.com/hadolint/hadolint/wiki/DL3003) # hadolint/DL3004 Source: https://tally.wharflab.com/rules/hadolint/DL3004 Do not use sudo. Do not use sudo. | Property | Value | | -------- | -------- | | Severity | Error | | Category | Security | | Default | Enabled | ## Description Do not use `sudo` as it leads to unpredictable behavior. Use a tool like [gosu](https://github.com/tianon/gosu) to enforce root. ## Examples ### Problematic code ```dockerfile theme={null} FROM busybox RUN sudo apt-get install ``` ### Correct code ```dockerfile theme={null} FROM busybox RUN apt-get install ``` ## Reference * [hadolint/DL3004](https://github.com/hadolint/hadolint/wiki/DL3004) # hadolint/DL3006 Source: https://tally.wharflab.com/rules/hadolint/DL3006 Always tag the version of an image explicitly. Always tag the version of an image explicitly. | Property | Value | | -------- | ------------- | | Severity | Warning | | Category | Best Practice | | Default | Enabled | ## Description You can never rely that the `latest` tag is a specific version. Always tag the version of an image explicitly to ensure reproducible builds. ## Examples ### Problematic code ```dockerfile theme={null} FROM debian ``` ### Correct code ```dockerfile theme={null} FROM debian:jessie ``` ### Exception When the image name refers to a previously defined alias, tagging is not required: ```dockerfile theme={null} FROM debian:jessie as build RUN build_script FROM build as tests RUN test_script FROM debian:jessie COPY --from=build foo . ``` ## Reference * [hadolint/DL3006](https://github.com/hadolint/hadolint/wiki/DL3006) # hadolint/DL3007 Source: https://tally.wharflab.com/rules/hadolint/DL3007 Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag. Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag. | Property | Value | | -------- | ------------- | | Severity | Warning | | Category | Best Practice | | Default | Enabled | ## Description You can never rely that the `latest` tag is a specific version. Pin the version explicitly to a release tag to ensure reproducible builds. ## Examples ### Problematic code ```dockerfile theme={null} FROM debian:latest ``` ### Correct code ```dockerfile theme={null} FROM debian:jessie ``` ## Reference * [hadolint/DL3007](https://github.com/hadolint/hadolint/wiki/DL3007) # hadolint/DL3010 Source: https://tally.wharflab.com/rules/hadolint/DL3010 Use ADD for extracting archives into an image. Use ADD for extracting archives into an image. | Property | Value | | -------- | ------------- | | Severity | Info | | Category | Best Practice | | Default | Enabled | ## Description Although `ADD` and `COPY` are functionally similar, generally speaking, `COPY` is preferred. `COPY` only supports the basic copying of local files into the container, while `ADD` has some features (like local-only tar extraction and remote URL support). The best use for `ADD` is local tar file auto-extraction into the image. ## Reference * [hadolint/DL3010](https://github.com/hadolint/hadolint/wiki/DL3010) # hadolint/DL3011 Source: https://tally.wharflab.com/rules/hadolint/DL3011 Valid UNIX ports range from 0 to 65535. Valid UNIX ports range from 0 to 65535. | Property | Value | | -------- | ----------- | | Severity | Error | | Category | Correctness | | Default | Enabled | ## Description Valid UNIX ports range from 0 to 65535. Exposing a port outside this range is invalid and will result in an error. ## Examples ### Problematic code ```dockerfile theme={null} FROM busybox EXPOSE 80000 ``` ### Correct code ```dockerfile theme={null} FROM busybox EXPOSE 65535 ``` ## Reference * [hadolint/DL3011](https://github.com/hadolint/hadolint/wiki/DL3011) # hadolint/DL3012 Source: https://tally.wharflab.com/rules/hadolint/DL3012 Multiple `HEALTHCHECK` instructions. Multiple `HEALTHCHECK` instructions. > **Superseded by > [`buildkit/MultipleInstructionsDisallowed`](../buildkit/MultipleInstructionsDisallowed)**, > which covers duplicate `HEALTHCHECK`, `CMD`, and `ENTRYPOINT` > instructions with auto-fix support. ## Description Multiple `HEALTHCHECK` instructions should not be used in the same stage because only the last one will be used. ## Reference * [hadolint/DL3012](https://github.com/hadolint/hadolint/wiki/DL3012) # hadolint/DL3014 Source: https://tally.wharflab.com/rules/hadolint/DL3014 Use the `-y` switch. Use the `-y` switch. | Property | Value | | -------- | ------------- | | Severity | Warning | | Category | Best Practice | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description Without the `--assume-yes` (`-y`) option it might be possible that the build breaks without human intervention. Always use `-y` with `apt-get install` to avoid interactive prompts during the build. ## Examples ### Problematic code ```dockerfile theme={null} FROM debian RUN apt-get install python=2.7 ``` ### Correct code ```dockerfile theme={null} FROM debian RUN apt-get install -y python=2.7 ``` ## Auto-fix Adds `-y` flag to `apt-get install` commands. ```dockerfile theme={null} # Before RUN apt-get install curl # After (with --fix) RUN apt-get install -y curl ``` ## Reference * [hadolint/DL3014](https://github.com/hadolint/hadolint/wiki/DL3014) # hadolint/DL3020 Source: https://tally.wharflab.com/rules/hadolint/DL3020 Use `COPY` instead of `ADD` for files and folders. Use `COPY` instead of `ADD` for files and folders. | Property | Value | | -------- | ------------- | | Severity | Error | | Category | Best Practice | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description For files and directories that do not require `ADD`'s tar auto-extraction capability, you should always use `COPY`. `COPY` is more transparent and predictable than `ADD`, since it only supports basic copying of files into the container. **Exception:** `ADD` is appropriate for local tar file auto-extraction into the image. ## Examples ### Problematic code ```dockerfile theme={null} FROM python:3.4 ADD requirements.txt /usr/src/app/ ``` ### Correct code ```dockerfile theme={null} FROM python:3.4 COPY requirements.txt /usr/src/app/ ``` ## Auto-fix Replaces the `ADD` keyword with `COPY`, preserving all flags, sources, and destination unchanged. * **Safe fix** (`FixSafe`): Always correct for local file/directory sources since `COPY` and `ADD` behave identically for non-URL, non-tar sources. ```dockerfile theme={null} # Before ADD --chown=app:app src/ /app/ # After (with --fix) COPY --chown=app:app src/ /app/ ``` The fix preserves instruction casing (`ADD` → `COPY`, `add` → `copy`). ## Reference * [hadolint/DL3020](https://github.com/hadolint/hadolint/wiki/DL3020) # hadolint/DL3021 Source: https://tally.wharflab.com/rules/hadolint/DL3021 `COPY` with more than 2 arguments requires the last argument to end with `/`. `COPY` with more than 2 arguments requires the last argument to end with `/`. | Property | Value | | -------- | ----------- | | Severity | Error | | Category | Correctness | | Default | Enabled | ## Description If multiple source resources are specified in a `COPY` instruction, then the destination must be a directory and it must end with a slash `/`. Omitting the trailing slash causes ambiguity and may lead to build errors or unexpected behavior. ## Examples ### Problematic code ```dockerfile theme={null} FROM node:carbon COPY package.json yarn.lock my_app ``` ### Correct code ```dockerfile theme={null} FROM node:carbon COPY package.json yarn.lock my_app/ ``` ## Reference * [hadolint/DL3021](https://github.com/hadolint/hadolint/wiki/DL3021) # hadolint/DL3022 Source: https://tally.wharflab.com/rules/hadolint/DL3022 `COPY --from` should reference a previously defined `FROM` alias. `COPY --from` should reference a previously defined `FROM` alias. | Property | Value | | -------- | ----------- | | Severity | Warning | | Category | Correctness | | Default | Off | ## Description When using multi-stage builds, `COPY --from` should reference a stage alias that was previously defined with `FROM ... AS `. Trying to copy from a missing image alias results in an error at build time. ### Why default Off This rule is off by default because it does not account for [`--build-context`](https://docs.docker.com/reference/cli/docker/buildx/build/#build-context) sources. As per the official Dockerfile documentation: > You can also copy files directly from named contexts > (specified with `--build-context =`) or images. Since named build contexts are supplied at build time (`docker buildx build --build-context name=path`), a static linter cannot verify whether a `COPY --from=name` reference is valid. Flagging these as violations produces false positives that cannot be resolved without running the actual build. To enable this rule, set its severity explicitly in your `.tally.toml`: ```toml theme={null} [rules.hadolint.DL3022] severity = "warning" ``` ## Examples ### Problematic code ```dockerfile theme={null} FROM debian:jesse RUN stuff FROM debian:jesse COPY --from=build some stuff ./ ``` ### Correct code ```dockerfile theme={null} FROM debian:jesse as build RUN stuff FROM debian:jesse COPY --from=build some stuff ./ ``` ## References * [hadolint/DL3022](https://github.com/hadolint/hadolint/wiki/DL3022) * [`COPY --from`](https://docs.docker.com/reference/dockerfile/#copy---from) # hadolint/DL3023 Source: https://tally.wharflab.com/rules/hadolint/DL3023 `COPY --from` cannot reference its own `FROM` alias. `COPY --from` cannot reference its own `FROM` alias. | Property | Value | | -------- | ----------- | | Severity | Error | | Category | Correctness | | Default | Enabled | ## Description A `COPY --from` instruction must not reference the same stage it is running in. Trying to copy from the current stage results in an error because the stage has not yet been finalized. ## Examples ### Problematic code ```dockerfile theme={null} FROM debian:jesse as build COPY --from=build some stuff ./ ``` ### Correct code ```dockerfile theme={null} FROM debian:jesse as build RUN stuff FROM debian:jesse COPY --from=build some stuff ./ ``` ## Reference * [hadolint/DL3023](https://github.com/hadolint/hadolint/wiki/DL3023) # hadolint/DL3024 Source: https://tally.wharflab.com/rules/hadolint/DL3024 Duplicate stage names are not allowed. Duplicate stage names are not allowed. > **Superseded by [`buildkit/DuplicateStageName`](../buildkit/DuplicateStageName)**, which provides the same check. ## Description Defining multiple stages with the same name results in an error because the builder is unable to uniquely resolve the stage name reference. ## Examples ### Problematic code ```dockerfile theme={null} FROM debian:latest AS builder RUN apt-get update FROM golang:latest AS builder ``` ### Correct code ```dockerfile theme={null} FROM debian:latest AS deb-builder RUN apt-get update FROM golang:latest AS go-builder ``` ## Reference * [hadolint/DL3024](https://github.com/hadolint/hadolint/wiki/DL3024) # hadolint/DL3025 Source: https://tally.wharflab.com/rules/hadolint/DL3025 Use arguments JSON notation for CMD and ENTRYPOINT arguments. Use arguments JSON notation for CMD and ENTRYPOINT arguments. > **Superseded by [`buildkit/JSONArgsRecommended`](../buildkit/JSONArgsRecommended)**, which provides the same check with auto-fix support. ## Description `ENTRYPOINT` and `CMD` instructions should use the JSON (exec) form to ensure proper OS signal handling. Shell form runs commands as a child process of `/bin/sh`, which doesn't pass signals like `SIGTERM`. ## Examples ### Problematic code ```dockerfile theme={null} FROM alpine CMD my-program start ``` ### Correct code ```dockerfile theme={null} FROM alpine CMD ["my-program", "start"] ``` ## Reference * [hadolint/DL3025](https://github.com/hadolint/hadolint/wiki/DL3025) # hadolint/DL3026 Source: https://tally.wharflab.com/rules/hadolint/DL3026 Use only an allowed registry in the `FROM` image. Use only an allowed registry in the `FROM` image. | Property | Value | | -------- | ------------------------------- | | Severity | Off | | Category | Security | | Default | Off (disabled until configured) | ## Description Using the `FROM` instruction is a significant exercise in trust. Some organizations copy trusted images into their own repositories to prevent malicious retagging. This rule enforces that only images from explicitly allowed registries are used. This rule is disabled by default and must be configured with a list of trusted registries to take effect. ## Examples ### Problematic code ```dockerfile theme={null} FROM randomguy/python:3.6 ``` ### Correct code ```dockerfile theme={null} FROM my-registry.com/python:3.6 ``` ## tally enhancements tally extends the original Hadolint rule with: * **Wildcard support**: `*` matches any registry, `*.example.com` matches any subdomain (suffix match), `prefix*` matches registries starting with prefix * **Docker Hub normalization**: `docker.io`, `index.docker.io`, `registry-1.docker.io`, `registry.hub.docker.com`, and `hub.docker.com` are all normalized to `docker.io` * **Stage references**: Automatically skips stage-to-stage references (`FROM stagename`) * **Scratch always allowed**: The special `scratch` base image is always permitted ### Configuration ```toml theme={null} [rules.hadolint.DL3026] severity = "warning" trusted-registries = ["docker.io", "gcr.io", "*.example.com"] ``` ## Reference * [hadolint/DL3026](https://github.com/hadolint/hadolint/wiki/DL3026) # hadolint/DL3027 Source: https://tally.wharflab.com/rules/hadolint/DL3027 Do not use `apt` as it is meant to be an end-user tool, use `apt-get` or `apt-cache` instead. Do not use `apt` as it is meant to be an end-user tool, use `apt-get` or `apt-cache` instead. | Property | Value | | -------- | ------------- | | Severity | Warning | | Category | Best Practice | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description `apt` is discouraged by the Linux distributions as an unattended tool as its interface may change between versions. Use the more stable `apt-get` and `apt-cache` commands in Dockerfiles instead. ## Examples ### Problematic code ```dockerfile theme={null} FROM busybox RUN apt install curl=1.1.0 ``` ### Correct code ```dockerfile theme={null} FROM busybox RUN apt-get install curl=1.1.0 ``` ## Auto-fix Replaces `apt` with `apt-get` or `apt-cache` depending on the subcommand. * **Safe fix** (`FixSafe`): `install`, `remove`, `update`, `upgrade`, `autoremove`, `purge`, `clean`, `autoclean` are replaced with `apt-get` * **Suggestion fix** (`FixSuggestion`): `search`, `show`, `policy` are replaced with `apt-cache` ```dockerfile theme={null} # Before RUN apt install -y curl && apt search python # After (with --fix) RUN apt-get install -y curl && apt-cache search python ``` ## Reference * [hadolint/DL3027](https://github.com/hadolint/hadolint/wiki/DL3027) # hadolint/DL3029 Source: https://tally.wharflab.com/rules/hadolint/DL3029 Do not use --platform flag with constant value in FROM. Do not use --platform flag with constant value in FROM. > **Superseded by [`buildkit/FromPlatformFlagConstDisallowed`](../buildkit/FromPlatformFlagConstDisallowed)**, which provides the same check with > improved diagnostics. ## Description Using a hardcoded `--platform` value in `FROM` restricts image building to a single target platform. Use `$BUILDPLATFORM` or similar variables instead. ## Examples ### Problematic code ```dockerfile theme={null} FROM --platform=linux/amd64 alpine ``` ### Correct code ```dockerfile theme={null} FROM --platform=${BUILDPLATFORM} alpine ``` ## Reference * [hadolint/DL3029](https://github.com/hadolint/hadolint/wiki/DL3029) # hadolint/DL3030 Source: https://tally.wharflab.com/rules/hadolint/DL3030 Use the `-y` switch to avoid manual input: `yum install -y `. Use the `-y` switch to avoid manual input: `yum install -y `. | Property | Value | | -------- | ------------- | | Severity | Warning | | Category | Best Practice | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description Without the `-y` flag or the equivalent `--assumeyes` flag, `yum` will not successfully install a package because human input is expected. In a Dockerfile `RUN` instruction there is no interactive terminal, so the build will fail. ## Examples ### Problematic code ```dockerfile theme={null} FROM centos RUN yum install httpd-2.24.4 && yum clean all ``` ### Correct code ```dockerfile theme={null} FROM centos RUN yum install -y httpd-2.24.4 && yum clean all ``` ## Auto-fix Adds `-y` flag to `yum install`, `groupinstall`, `localinstall`, and `reinstall` commands. ## Reference * [hadolint/DL3030](https://github.com/hadolint/hadolint/wiki/DL3030) # hadolint/DL3034 Source: https://tally.wharflab.com/rules/hadolint/DL3034 Non-interactive switch missing from `zypper` command: `zypper install -y`. Non-interactive switch missing from `zypper` command: `zypper install -y`. | Property | Value | | -------- | ------------- | | Severity | Warning | | Category | Best Practice | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description Omitting the non-interactive switch causes the command to fail during the build process because `zypper` would expect manual input. Use the `-y` or the equivalent `--no-confirm` flag to ensure unattended operation inside a Dockerfile. ## Examples ### Problematic code ```dockerfile theme={null} RUN zypper install httpd=2.4.46 && zypper clean ``` ### Correct code ```dockerfile theme={null} RUN zypper install -y httpd=2.4.46 && zypper clean ``` ## Auto-fix Adds `-n` (non-interactive) flag to `zypper install`, `in`, `remove`, `rm`, `patch`, `source-install`, and `si` commands. ## Reference * [hadolint/DL3034](https://github.com/hadolint/hadolint/wiki/DL3034) # hadolint/DL3038 Source: https://tally.wharflab.com/rules/hadolint/DL3038 Use the `-y` switch to avoid manual input: `dnf install -y `. Use the `-y` switch to avoid manual input: `dnf install -y `. | Property | Value | | -------- | ------------- | | Severity | Warning | | Category | Best Practice | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description Omitting the non-interactive switch causes the command to fail during the build process because `dnf` would expect manual input. Use the `-y` or the equivalent `--assumeyes` flag to ensure unattended operation inside a Dockerfile. ## Examples ### Problematic code ```dockerfile theme={null} FROM fedora:32 RUN dnf install httpd-2.4.46 && dnf clean all ``` ### Correct code ```dockerfile theme={null} FROM fedora:32 RUN dnf install -y httpd-2.4.46 && dnf clean all ``` ## Auto-fix Adds `-y` flag to `dnf` and `microdnf` `install`, `groupinstall`, and `localinstall` commands. ## Reference * [hadolint/DL3038](https://github.com/hadolint/hadolint/wiki/DL3038) # hadolint/DL3043 Source: https://tally.wharflab.com/rules/hadolint/DL3043 `ONBUILD`, `FROM` or `MAINTAINER` triggered from within `ONBUILD` instruction. `ONBUILD`, `FROM` or `MAINTAINER` triggered from within `ONBUILD` instruction. | Property | Value | | -------- | ----------- | | Severity | Error | | Category | Correctness | | Default | Enabled | ## Description The `ONBUILD` instruction does not allow `ONBUILD`, `FROM`, or `MAINTAINER` as nested instructions. Using any of these within an `ONBUILD` trigger is an error and will cause a build failure. ## Examples ### Problematic code ```dockerfile theme={null} ONBUILD ONBUILD /bin/true ONBUILD FROM debian ONBUILD MAINTAINER Ron Weasley ``` ## Reference * [hadolint/DL3043](https://github.com/hadolint/hadolint/wiki/DL3043) * [Dockerfile ONBUILD reference](https://docs.docker.com/engine/reference/builder/#onbuild) # hadolint/DL3044 Source: https://tally.wharflab.com/rules/hadolint/DL3044 Do not refer to an undefined variable. Do not refer to an undefined variable. > **Superseded by [`buildkit/UndefinedVar`](../buildkit/UndefinedVar)**, which provides the same check with typo detection. ## Description Ensures that environment variables and build arguments are declared before being used. Undeclared variables can lead to unexpected behavior or build errors. ## Examples ### Problematic code ```dockerfile theme={null} FROM alpine COPY $foo . ``` ### Correct code ```dockerfile theme={null} FROM alpine ARG foo COPY $foo . ``` ## Reference * [hadolint/DL3044](https://github.com/hadolint/hadolint/wiki/DL3044) # hadolint/DL3045 Source: https://tally.wharflab.com/rules/hadolint/DL3045 `COPY` to a relative destination without `WORKDIR` set. `COPY` to a relative destination without `WORKDIR` set. | Property | Value | | -------- | ---------------------------------------------------------- | | Severity | Warning | | Category | Best Practice | | Default | Enabled | | Auto-fix | Yes (suggestion `--fix-unsafe`; safe with `--slow-checks`) | ## Description While copying to a relative path is not problematic per se, errors happen when changes are introduced to the `WORKDIR` without updating the destination of the `COPY` command. It is assumed that when a `WORKDIR` is set, the programmer will make sure it works well together with the `COPY` destinations. ## Examples ### Problematic code ```dockerfile theme={null} FROM scratch COPY foo bar ``` ### Correct code ```dockerfile theme={null} FROM scratch COPY foo /bar ``` or: ```dockerfile theme={null} FROM scratch WORKDIR / COPY foo bar ``` ## Auto-fix Inserts an explicit `WORKDIR` instruction before the first `COPY` with a relative destination. * **Fast path** (no registry access): suggests `WORKDIR /app` as a conventional default (`FixSuggestion`, requires `--fix-unsafe`). * **With `--slow-checks`**: resolves the base image's actual `WORKDIR` from the registry and uses that value (`FixSafe`, applied with `--fix`). ```dockerfile theme={null} # Before FROM python:3.12 COPY requirements.txt . # After (with --fix-unsafe, fast path) FROM python:3.12 WORKDIR /app COPY requirements.txt . # After (with --fix --slow-checks, base image has WORKDIR /usr/src/app) FROM python:3.12 WORKDIR /usr/src/app COPY requirements.txt . ``` ## Reference * [hadolint/DL3045](https://github.com/hadolint/hadolint/wiki/DL3045) # hadolint/DL3046 Source: https://tally.wharflab.com/rules/hadolint/DL3046 `useradd` without flag `-l` and high UID will result in excessively large image. `useradd` without flag `-l` and high UID will result in excessively large image. | Property | Value | | -------- | ------------- | | Severity | Warning | | Category | Performance | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description Without the `-l` or `--no-log-init` flag, `useradd` will add the user to the lastlog and faillog databases. This can result in the creation of logically large (sparse) files under `/var/log`, which inflates container image sizes due to the lack of support for sparse files in overlay filesystems. ## Examples ### Problematic code ```dockerfile theme={null} RUN useradd -u 123456 foobar ``` ### Correct code ```dockerfile theme={null} RUN useradd -l -u 123456 foobar ``` ## Auto-fix Inserts `-l` flag after `useradd` when UID is greater than 99999 and `-l`/`--no-log-init` is not already present. ```dockerfile theme={null} # Before RUN useradd -u 100001 appuser # After (with --fix) RUN useradd -u 100001 -l appuser ``` ## Reference * [hadolint/DL3046](https://github.com/hadolint/hadolint/wiki/DL3046) # hadolint/DL3047 Source: https://tally.wharflab.com/rules/hadolint/DL3047 Use `wget --progress` to avoid excessively bloated build logs. Use `wget --progress` to avoid excessively bloated build logs. | Property | Value | | -------- | ------------- | | Severity | Info | | Category | Best Practice | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description `wget` without flag `--progress` will result in excessively bloated build logs when downloading larger files because it outputs a line for each fraction of a percentage point. ## Examples ### Problematic code ```dockerfile theme={null} FROM ubuntu:20 RUN wget https://example.com/big_file.tar ``` ### Correct code ```dockerfile theme={null} FROM ubuntu:20 RUN wget --progress=dot:giga https://example.com/big_file.tar ``` or: ```dockerfile theme={null} FROM ubuntu:20 RUN wget -nv https://example.com/big_file.tar ``` ## Auto-fix Adds `--progress=dot:giga` to wget commands. Skipped if `-q`, `--quiet`, `-nv`, `--no-verbose`, `-o`, `--output-file`, `-a`, `--append-output`, or `--progress` is already present. ```dockerfile theme={null} # Before RUN wget http://example.com/file.tar.gz # After (with --fix) RUN wget --progress=dot:giga http://example.com/file.tar.gz ``` ## Reference * [hadolint/DL3047](https://github.com/hadolint/hadolint/wiki/DL3047) # hadolint/DL3057 Source: https://tally.wharflab.com/rules/hadolint/DL3057 `HEALTHCHECK` instruction missing. `HEALTHCHECK` instruction missing. | Property | Value | | -------- | ----------------------- | | Severity | Ignore (off by default) | | Category | Best Practice | | Default | Off | ## Description This is an optional rule. When it is required to define a health check (e.g. by company policy), it must not be omitted. This rule is disabled by default because a `HEALTHCHECK` is not desirable in all circumstances. Images used with Kubernetes do not benefit from a `HEALTHCHECK` instruction, as Kubernetes brings its own mechanisms. ## Examples ### Problematic code ```dockerfile theme={null} FROM busybox ``` ### Correct code ```dockerfile theme={null} FROM busybox HEALTHCHECK CMD /bin/health ``` or: ```dockerfile theme={null} FROM busybox HEALTHCHECK NONE ``` ## tally enhancements ### Smart suppression tally automatically suppresses this rule when the Dockerfile shows strong signals that a `HEALTHCHECK` would not be beneficial: #### Serverless / FaaS base images Containers built for serverless platforms have their lifecycle managed externally — the platform decides when to start, stop, and replace function instances. A container-level `HEALTHCHECK` is ignored in these environments. | Platform | Suppressed image patterns | | ------------------- | ---------------------------------------------------------------------------------- | | **AWS Lambda** | `public.ecr.aws/lambda/*`, `gallery.ecr.aws/lambda/*`, `amazon/aws-lambda-*` | | **Azure Functions** | `mcr.microsoft.com/azure-functions/*` | | **OpenFaaS** | `openfaas/of-watchdog`, `openfaas/classic-watchdog` (including `ghcr.io` variants) | If **any** stage in the Dockerfile uses a recognized serverless base image, the violation is suppressed for the entire file. Multi-stage builds that pull from a Lambda image in one stage and copy artifacts into another are still covered because the presence of the serverless image signals the target runtime. #### Serverless framework entrypoints When the **final stage's** `CMD` or `ENTRYPOINT` invokes a known serverless function framework, the container is a short-lived function handler managed by the platform — not a service that benefits from `HEALTHCHECK`. | Framework | Example | | -------------------------------------------------- | ----------------------------------------------- | | **Google Cloud Functions** (`functions-framework`) | `CMD ["functions-framework", "--target=hello"]` | The `exec` prefix commonly used in shell form is handled: ```dockerfile theme={null} CMD exec functions-framework --target=hello --port=$PORT ``` #### Interactive / shell-only containers When the **final stage's** `CMD` or `ENTRYPOINT` resolves to a bare interactive shell (`sh`, `bash`, `zsh`, `ash`, `dash`, `fish`, `csh`, `tcsh`, `ksh`), the container is clearly not a long-running service — there is no endpoint to health-check. Recognized patterns: ```dockerfile theme={null} CMD ["bash"] # exec form CMD bash # shell form CMD ["bash", "-l"] # shell with flags (still interactive) ENTRYPOINT ["/bin/sh"] # entrypoint shell ``` Not suppressed when the shell is used to execute a command: ```dockerfile theme={null} CMD ["bash", "-c", "my-app"] # runs my-app, not interactive ``` If an `ENTRYPOINT` is present, it takes precedence over `CMD` (matching Docker runtime semantics). #### No explicit CMD/ENTRYPOINT (external parent delegation) When the **final stage** has no `CMD` or `ENTRYPOINT` instruction and its base is an **external image** (not another build stage), the image delegates run orchestration to its parent. In these cases the parent likely also defines a `HEALTHCHECK`, so flagging the child produces false positives. The violation is suppressed. ```dockerfile theme={null} FROM nginx:latest RUN echo "custom config" > /etc/nginx/conf.d/default.conf EXPOSE 80 # No CMD — nginx base image provides CMD and likely HEALTHCHECK ``` This does **not** apply when the final stage inherits from a prior build stage (`FROM `), because CMD/ENTRYPOINT are inherited from the prior stage and the image is not opaque: ```dockerfile theme={null} FROM alpine AS base CMD ["my-app"] FROM base RUN echo "setup" # DL3057 still fires — CMD inherited from "base", image is not opaque ``` ### Explicit opt-out with `HEALTHCHECK NONE` `HEALTHCHECK NONE` is treated as a deliberate opt-out. When present in any stage, DL3057 is fully suppressed — no fast-path violation is emitted and no async registry checks are planned. This matches Docker's semantics where `HEALTHCHECK NONE` explicitly disables health checking. ### Async registry resolution tally extends this rule with **async registry resolution** (enabled with `--slow-checks`): * **Base image inspection**: For each external base image, tally checks if it already defines a `HEALTHCHECK` in its image metadata. If so, the violation is suppressed since the health check is inherited. This check is skipped when any explicit `HEALTHCHECK` instruction (CMD or NONE) is already present. * **Cross-rule awareness**: `buildkit/MultipleInstructionsDisallowed` may still flag duplicate `HEALTHCHECK` instructions even when DL3057 is suppressed. ## Reference * [hadolint/DL3057](https://github.com/hadolint/hadolint/wiki/DL3057) # hadolint/DL3059 Source: https://tally.wharflab.com/rules/hadolint/DL3059 Multiple consecutive `RUN` instructions. Multiple consecutive `RUN` instructions. > **Superseded by > [`tally/prefer-run-heredoc`](../tally/prefer-run-heredoc)**, > which provides the same check with enhanced detection > (consecutive RUNs and chained commands) and auto-fix support > using heredoc syntax. ## Description Multiple consecutive `RUN` instructions can be consolidated into a single instruction to reduce image layers. ## Examples ### Problematic code ```dockerfile theme={null} FROM ubuntu RUN apt-get update RUN apt-get install -y curl ``` ### Correct code ```dockerfile theme={null} FROM ubuntu RUN apt-get update && apt-get install -y curl ``` Or with heredoc syntax (tally auto-fix): ```dockerfile theme={null} FROM ubuntu RUN < **Superseded by [`buildkit/ReservedStageName`](../buildkit/ReservedStageName)**, which provides the same check. ## Description Stage aliases in `FROM ... AS ` must not use reserved Dockerfile stage names such as `scratch` or `context`. ## Examples ### Problematic code ```dockerfile theme={null} FROM alpine:3.21 AS scratch ``` ### Correct code ```dockerfile theme={null} FROM alpine:3.21 AS builder ``` ## Reference * [hadolint/DL3063](https://github.com/hadolint/hadolint/wiki/DL3063) # hadolint/DL4000 Source: https://tally.wharflab.com/rules/hadolint/DL4000 MAINTAINER is deprecated. MAINTAINER is deprecated. > **Superseded by [`buildkit/MaintainerDeprecated`](../buildkit/MaintainerDeprecated)**, which provides the same check with auto-fix support. ## Description The `MAINTAINER` instruction is deprecated. Use the `org.opencontainers.image.authors` label instead. ## Examples ### Problematic code ```dockerfile theme={null} MAINTAINER moby@example.com ``` ### Correct code ```dockerfile theme={null} LABEL org.opencontainers.image.authors="moby@example.com" ``` ## Reference * [hadolint/DL4000](https://github.com/hadolint/hadolint/wiki/DL4000) # hadolint/DL4001 Source: https://tally.wharflab.com/rules/hadolint/DL4001 Either use Wget or Curl but not both. Either use Wget or Curl but not both. | Property | Value | | -------- | ----------------------------------------------- | | Severity | Warning | | Category | Best Practice | | Default | Enabled | | Auto-fix | Yes (`--fix --fix-unsafe`; AI AutoFix fallback) | ## Description Don't install two tools that have the same effect. Using both `wget` and `curl` in a Dockerfile adds unnecessary cruft to the image. Pick one and use it consistently. The rule fires when both tools are present in the Dockerfile in any combination: both invoked, both installed, or one installed while the other is invoked. Installing the second tool is itself the offense — even if you never call it, it still inflates the image. ## Examples ### Problematic code ```dockerfile theme={null} FROM debian RUN wget http://google.com RUN curl http://bing.com ``` ### Correct code ```dockerfile theme={null} FROM debian RUN curl http://google.com RUN curl http://bing.com ``` ## Auto-fix The rule rewrites offending commands to the tool it picks as the winner for the Dockerfile. Rewriting is **bidirectional**: `curl → wget` or `wget → curl`. All fixes are **unsafe** (`FixUnsafe`) and require `--fix --fix-unsafe`. ### How the winning tool is chosen (auto mode) Auto mode uses usage-based heuristics, not tool-install flags. The winner — and therefore the direction of the rewrite — is decided in this order: 1. **Used without explicit install wins.** If exactly one of `curl` / `wget` is used without being explicitly installed by the Dockerfile (e.g., it comes from the base image), that tool wins and the other one is rewritten. Installing the tool you already get "for free" is the thing DL4001 is pushing back on. 2. **Most invocations wins.** If both tools are in the same install category (both installed, or both used-without-install), the tool with more total invocations across the Dockerfile wins. 3. **First seen wins.** If invocation counts tie, the tool whose first use appears earlier in the Dockerfile wins. The fix runs in two phases: * **Rewrite each invocation.** Tally doesn't try to translate `curl` flags into the equivalent `wget` flags argument-by-argument — that's a fragile game with many edge cases. Instead, each offending command is read as a single HTTP action ("download this URL into this file", "download this URL and pipe it into `tar`", etc.), and the replacement is produced from scratch using whichever target tool was chosen. What matters for a Docker image build is the *outcome* — which bytes end up where, which errors fail the build — and that's what the rewrite preserves. When a command is too elaborate to reinterpret this way (non-HTTP features, shell interpolation, custom scripting), tally falls back to the AI AutoFix path, which additionally requires an ACP-capable agent configured in the top-level `[ai]` section. * **Clean up what's left.** After all the invocation rewrites are applied, tally re-reads the resulting Dockerfile and removes anything that exists only to serve the evicted tool: * the tool's entry in install commands (dropped from the package list, or the whole install RUN if it only installed that one tool), * COPY heredocs that write the tool's config file (`.curlrc` / `.wgetrc` / `/etc/wgetrc`), * `ENV` bindings that point at those config paths (`CURL_HOME`, `WGETRC`, `WGETHOSTS`), * any annotation comments tally itself added when it inserted the config. Doing the cleanup after the main rewrite is what lets DL4001 play nicely with peer rules. If `tally/sort-packages` reorders the install line or `tally/prefer-curl-config` inserts a `.curlrc` heredoc, those edits are already applied by the time cleanup runs, so the cleanup sees the finished state and does the right thing instead of fighting anyone for the same source range. The rule refuses to attach a sync fix when the preferred tool already appears in the same `RUN` (for example `curl ... || wget ...`). On Windows stages the replacement is emitted with a `.exe` suffix (`curl.exe`, `wget.exe`); on Linux stages the bare tool name is used. ### Deterministic rewrite examples ```dockerfile theme={null} # Before — curl is explicitly installed, wget comes from the base image, # so wget wins (used-without-install) and the installed curl is rewritten. RUN apt-get update && apt-get install -y curl RUN curl -fsSL https://example.com/bootstrap.tgz RUN wget https://example.com/file.tgz # After (with --fix --fix-unsafe) RUN apt-get update && apt-get install -y curl RUN wget -nv -O- https://example.com/bootstrap.tgz RUN wget https://example.com/file.tgz ``` ```dockerfile theme={null} # Before — both tools are installed and both are used. curl wins the first-seen # tie-break, and wget is dropped from the install list so it isn't pulled at all. RUN apt-get update && apt-get install -y curl wget RUN curl https://example.com/bootstrap.tgz RUN wget https://example.com/file.tgz # After (with --fix --fix-unsafe) RUN apt-get update && apt-get install -y curl RUN curl https://example.com/bootstrap.tgz RUN curl -fL -O https://example.com/file.tgz ``` ```dockerfile theme={null} # Before — neither tool is installed (both come from the base image) and # curl is used twice vs wget once, so curl wins the count tie-break. RUN wget https://example.com/file1 RUN curl https://example.com/file2 RUN curl https://example.com/file3 # After (with --fix --fix-unsafe) RUN curl -fL -O https://example.com/file1 RUN curl https://example.com/file2 RUN curl https://example.com/file3 ``` ### When the AI AutoFix fallback is used Examples where deterministic lowering cannot preserve Dockerfile-relevant behavior and the rule falls back to AI AutoFix: ```dockerfile theme={null} # curl without -L: redirect-following semantics cannot be preserved deterministically. RUN curl -fsS -o /tmp/file https://example.com/file ``` ```dockerfile theme={null} # curl without -f: fail-on-HTTP-status semantics cannot be preserved deterministically. RUN curl -sSL https://example.com/app.tgz | tar -xz -C /opt ``` ## Configuration ### `fix-preference` * Type: `string` * Allowed values: `"auto"`, `"curl"`, `"wget"` * Default: `"auto"` Controls which tool auto-fixes converge on, and therefore which of the two offending tools is reported as the violation. * `"auto"` (default): pick the winner using the usage-based heuristics described in the Auto-fix section above (used-without-install → invocation count → first seen). * `"curl"`: always report and rewrite `wget` calls to `curl`, regardless of usage or install state. * `"wget"`: always report and rewrite `curl` calls to `wget`, regardless of usage or install state. Explicit preferences are useful when you want a project-wide convention and don't want auto mode's heuristics to decide per-file. An invalid value falls back to `"auto"`. ```toml theme={null} [rules.hadolint.DL4001] fix-preference = "curl" ``` ## Reference * [hadolint/DL4001](https://github.com/hadolint/hadolint/wiki/DL4001) # hadolint/DL4003 Source: https://tally.wharflab.com/rules/hadolint/DL4003 Multiple `CMD` instructions found. Multiple `CMD` instructions found. > **Superseded by > [`buildkit/MultipleInstructionsDisallowed`](../buildkit/MultipleInstructionsDisallowed)**, > which covers duplicate `CMD`, `ENTRYPOINT`, and `HEALTHCHECK` > instructions with auto-fix support. ## Description If more than one `CMD` instruction is listed, only the last one takes effect. ## Examples ### Problematic code ```dockerfile theme={null} FROM alpine CMD echo "first" CMD echo "second" ``` ### Correct code ```dockerfile theme={null} FROM alpine CMD echo "second" ``` ## Reference * [hadolint/DL4003](https://github.com/hadolint/hadolint/wiki/DL4003) # hadolint/DL4004 Source: https://tally.wharflab.com/rules/hadolint/DL4004 Multiple `ENTRYPOINT` instructions found. Multiple `ENTRYPOINT` instructions found. > **Superseded by > [`buildkit/MultipleInstructionsDisallowed`](../buildkit/MultipleInstructionsDisallowed)**, > which covers duplicate `CMD`, `ENTRYPOINT`, and `HEALTHCHECK` > instructions with auto-fix support. ## Description If more than one `ENTRYPOINT` instruction is listed, only the last one takes effect. ## Examples ### Problematic code ```dockerfile theme={null} FROM alpine ENTRYPOINT ["echo", "first"] ENTRYPOINT ["echo", "second"] ``` ### Correct code ```dockerfile theme={null} FROM alpine ENTRYPOINT ["echo", "second"] ``` ## Reference * [hadolint/DL4004](https://github.com/hadolint/hadolint/wiki/DL4004) # hadolint/DL4005 Source: https://tally.wharflab.com/rules/hadolint/DL4005 Use `SHELL` to change the default shell. Use `SHELL` to change the default shell. | Property | Value | | -------- | ------------- | | Severity | Warning | | Category | Best Practice | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description Docker provides a `SHELL` instruction which does not require overwriting `/bin/sh` in your container. Instead of using `ln -sf` to redirect `/bin/sh`, use the `SHELL` instruction to set the desired shell. ## Examples ### Problematic code ```dockerfile theme={null} RUN apk add --update-cache bash=4.3.42-r3 RUN ln -sfv /bin/bash /bin/sh ``` ### Correct code ```dockerfile theme={null} RUN apk add --update-cache bash=4.3.42-r3 SHELL ["/bin/bash", "-c"] ``` ## Auto-fix Replaces `ln -sf` targeting `/bin/sh` with a `SHELL` instruction. If the `ln` command is part of a larger `RUN`, the `ln` portion is removed and a `SHELL` instruction is inserted after. ```dockerfile theme={null} # Before RUN ln -sf /bin/bash /bin/sh && apk add curl # After (with --fix) RUN apk add curl SHELL ["/bin/bash", "-c"] ``` ## Reference * [hadolint/DL4005](https://github.com/hadolint/hadolint/wiki/DL4005) # hadolint/DL4006 Source: https://tally.wharflab.com/rules/hadolint/DL4006 Set the `SHELL` option `-o pipefail` before `RUN` with a pipe in. Set the `SHELL` option `-o pipefail` before `RUN` with a pipe in. | Property | Value | | -------- | ------------- | | Severity | Warning | | Category | Best Practice | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description Some `RUN` commands depend on the ability to pipe the output of one command into another using the pipe character (`|`). Docker executes these commands using `/bin/sh -c`, which only evaluates the exit code of the last operation in the pipe. Since there are some shells that do not accept the `-o pipefail` option, it is not enough to add `set -o pipefail` inside the `RUN` instruction. Therefore, we recommend always explicitly adding the `SHELL` instruction before using pipes in `RUN`. ## Examples ### Problematic code ```dockerfile theme={null} RUN wget -O - https://some.site | wc -l > /number ``` ### Correct code ```dockerfile theme={null} SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN wget -O - https://some.site | wc -l > /number ``` or for Alpine/busybox: ```dockerfile theme={null} SHELL ["/bin/ash", "-eo", "pipefail", "-c"] RUN wget -O - https://some.site | wc -l > /number ``` ## Auto-fix Inserts a `SHELL ["/bin/bash", "-o", "pipefail", "-c"]` instruction before the first `RUN` with a pipe in each stage. Only generated once per stage since `SHELL` persists. ```dockerfile theme={null} # Before RUN cmd1 | cmd2 | cmd3 # After (with --fix) SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN cmd1 | cmd2 | cmd3 ``` ## Reference * [hadolint/DL4006](https://github.com/hadolint/hadolint/wiki/DL4006) # Hadolint rules Source: https://tally.wharflab.com/rules/hadolint/overview tally implements Hadolint DL rules natively and covers additional ones via equivalent BuildKit and tally rules. [Hadolint](https://github.com/hadolint/hadolint) is a widely-used Dockerfile linter. tally implements its rules natively — so you get Hadolint-compatible checks without installing a separate tool — and adds enhancements like auto-fix and smart suppression. * DL rules implemented natively by tally * Additional rules covered by equivalent BuildKit or tally rules * Full Hadolint rule documentation: [github.com/hadolint/hadolint/wiki](https://github.com/hadolint/hadolint/wiki) tally supports the `# hadolint ignore=DLxxxx` directive format natively, so existing Hadolint suppressions work without any changes. The `# hadolint shell=powershell` directive is also supported. *** ## Implemented rules Rules natively implemented by tally. Auto-fixable rules are marked with 🔧. | Rule | Description | Severity | Notes | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------- | | `hadolint/DL3001` | For some bash commands it makes no sense running them in a Docker container like `ssh`, `vim`, `shutdown`, `service`, `ps`, `free`, `top`, `kill`, `mount`, `ifconfig`. | Info | | | `hadolint/DL3002` | Last user should not be root. | Warning | | | `hadolint/DL3003` 🔧 | Use WORKDIR to switch to a directory. | Warning | Auto-fixable | | `hadolint/DL3004` | Do not use `sudo` as it leads to unpredictable behavior. Use a tool like gosu to enforce root. | Error | | | `hadolint/DL3006` | Always tag the version of an image explicitly. | Warning | | | `hadolint/DL3007` | Using `latest` is prone to errors if the image will ever update. Pin the version explicitly to a release tag. | Warning | | | `hadolint/DL3010` | Use ADD for extracting archives into an image. | Info | | | `hadolint/DL3011` | Valid UNIX ports range from 0 to 65535. | Error | | | `hadolint/DL3014` 🔧 | Use the `-y` switch. | Warning | Auto-fixable | | `hadolint/DL3020` 🔧 | Use `COPY` instead of `ADD` for files and folders. | Error | Auto-fixable | | `hadolint/DL3021` | `COPY` with more than 2 arguments requires the last argument to end with `/`. | Error | | | `hadolint/DL3022` | `COPY --from` should reference a previously defined `FROM` alias. | Off | Off by default — does not account for `--build-context` sources | | `hadolint/DL3023` | `COPY --from` cannot reference its own `FROM` alias. | Error | | | `hadolint/DL3026` | Use only an allowed registry in the FROM image. | Off | Off by default — requires `trusted-registries` configuration | | `hadolint/DL3027` 🔧 | Do not use `apt` as it is meant to be an end-user tool; use `apt-get` or `apt-cache` instead. | Warning | Auto-fixable | | `hadolint/DL3030` 🔧 | Use the `-y` switch to avoid manual input: `yum install -y `. | Warning | Auto-fixable | | `hadolint/DL3034` 🔧 | Non-interactive switch missing from `zypper` command: `zypper install -y`. | Warning | Auto-fixable | | `hadolint/DL3038` 🔧 | Use the `-y` switch to avoid manual input: `dnf install -y `. | Warning | Auto-fixable | | `hadolint/DL3043` | `ONBUILD`, `FROM` or `MAINTAINER` triggered from within `ONBUILD` instruction. | Error | | | `hadolint/DL3045` 🔧 | `COPY` to a relative destination without `WORKDIR` set. | Warning | Auto-fixable | | `hadolint/DL3046` 🔧 | `useradd` without flag `-l` and a high UID will result in an excessively large image. | Warning | Auto-fixable | | `hadolint/DL3047` 🔧 | `wget` without flag `--progress` will result in excessively bloated build logs when downloading larger files. | Info | Auto-fixable | | `hadolint/DL3057` | `HEALTHCHECK` instruction missing. | Info | Enhanced: smart suppression for serverless/FaaS and registry-backed check with `--slow-checks` | | `hadolint/DL3061` | Invalid instruction order. Dockerfile must begin with `FROM`, `ARG`, or a comment. | Error | | | `hadolint/DL4001` | Either use Wget or Curl but not both. | Warning | | | `hadolint/DL4005` 🔧 | Use `SHELL` to change the default shell. | Warning | Auto-fixable | | `hadolint/DL4006` 🔧 | Set the `SHELL` option `-o pipefail` before `RUN` with a pipe in it. | Warning | Auto-fixable | ### Enabling off-by-default rules **DL3022** and **DL3026** are off by default and must be enabled in `.tally.toml`: ```toml theme={null} # Enable DL3026 with trusted registry enforcement [rules.hadolint.DL3026] trusted-registries = ["docker.io", "ghcr.io"] ``` Providing `trusted-registries` automatically enables the rule with `severity = "warning"`. Set `severity` explicitly to use a different level. *** ## Covered by BuildKit and tally rules These Hadolint rules are superseded by an equivalent BuildKit or tally rule. You do not need to enable both — tally's rule provides the same or better coverage. | Hadolint rule | Equivalent tally rule | Notes | | ------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | DL3000 | `buildkit/WorkdirRelativePath` 🔧 | Covers absolute WORKDIR enforcement | | DL3012 | `buildkit/MultipleInstructionsDisallowed` 🔧 | Covers multiple HEALTHCHECK instructions | | DL3024 | `buildkit/DuplicateStageName` | Covers duplicate FROM alias names | | DL3025 | `buildkit/JSONArgsRecommended` 🔧 | Covers JSON notation for CMD and ENTRYPOINT | | DL3029 | `buildkit/FromPlatformFlagConstDisallowed` + `tally/platform-mismatch` | `buildkit/FromPlatformFlagConstDisallowed` is off by default; `tally/platform-mismatch` provides a stricter registry-backed check | | DL3044 | `buildkit/UndefinedVar` | Covers referencing an ENV variable in the same ENV statement | | DL3059 | `tally/prefer-run-heredoc` 🔧 | Suggests heredoc syntax instead of consolidating consecutive RUN instructions | | DL3063 | `buildkit/ReservedStageName` | Covers reserved `scratch` and `context` FROM aliases | | DL4000 | `buildkit/MaintainerDeprecated` 🔧 | Covers deprecated MAINTAINER instruction | | DL4003 | `buildkit/MultipleInstructionsDisallowed` 🔧 | Covers multiple CMD instructions | | DL4004 | `buildkit/MultipleInstructionsDisallowed` 🔧 | Covers multiple ENTRYPOINT instructions | *** ## Not planned The following rules are intentionally not implemented. tally promotes BuildKit cache mounts via `tally/prefer-package-cache-mounts` as the modern alternative to manual cache-cleanup patterns. | Rule | Description | Reason not planned | | ------ | ------------------------------------------------------------------ | --------------------------------------------------------------- | | DL3009 | Delete the apt-get lists after installing something. | `tally/prefer-package-cache-mounts` is the recommended approach | | DL3015 | Avoid additional packages by specifying `--no-install-recommends`. | `tally/prefer-package-cache-mounts` is the recommended approach | | DL3019 | Use the `--no-cache` switch with `apk add`. | `tally/prefer-package-cache-mounts` is the recommended approach | | DL3032 | `yum clean all` missing after yum command. | `tally/prefer-package-cache-mounts` is the recommended approach | | DL3036 | `zypper clean` missing after zypper use. | `tally/prefer-package-cache-mounts` is the recommended approach | | DL3040 | `dnf clean all` missing after dnf command. | `tally/prefer-package-cache-mounts` is the recommended approach | | DL3042 | Avoid cache directory with `pip install --no-cache-dir`. | `tally/prefer-package-cache-mounts` is the recommended approach | | DL3060 | `yarn cache clean` missing after `yarn install`. | `tally/prefer-package-cache-mounts` is the recommended approach | Cache-cleanup instructions add build-time overhead and produce smaller layers at the cost of slower rebuilds. BuildKit cache mounts (`RUN --mount=type=cache`) solve the same problem more efficiently by keeping package caches on the host between builds. See `tally/prefer-package-cache-mounts` for details. *** ## DL3057: HEALTHCHECK instruction missing (enhanced) tally's implementation of DL3057 goes beyond Hadolint's static check with smart suppression and an optional registry-backed resolution path. ### Smart suppression (static) The rule is automatically suppressed when a `HEALTHCHECK` would not be beneficial: * **Serverless base images** — AWS Lambda (`public.ecr.aws/lambda/*`, `amazon/aws-lambda-*`), Azure Functions (`mcr.microsoft.com/azure-functions/*`), and OpenFaaS watchdog images. These platforms manage function lifecycle externally. * **Serverless framework entrypoints** — When the final stage's `CMD` or `ENTRYPOINT` invokes a known FaaS wrapper (e.g. `functions-framework` for Google Cloud Functions), including the common `exec` prefix pattern. * **Shell-only containers** — When the final stage's `CMD` or `ENTRYPOINT` is a bare interactive shell (`bash`, `sh`, etc.), the container is not a long-running service. ### Registry-backed resolution (`--slow-checks`) `HEALTHCHECK` is inherited from base images at runtime. If a base image defines `HEALTHCHECK CMD ...`, child images inherit it automatically. tally can resolve this by inspecting the base image registry. | Scenario | Fast path (static) | With `--slow-checks` | | ------------------------------------------------------------- | ----------------------------- | ------------------------------------------ | | No `HEALTHCHECK CMD` in Dockerfile, base has `HEALTHCHECK` | Violation (false positive) | Suppressed (inherited from base) | | No `HEALTHCHECK CMD` in Dockerfile, base has no `HEALTHCHECK` | Violation | Violation confirmed | | `HEALTHCHECK NONE` in Dockerfile, base has no `HEALTHCHECK` | Violation (generic "missing") | Specific: "HEALTHCHECK NONE has no effect" | When `--slow-checks` is off, only the fast static check runs. *** ## Migrating from Hadolint tally is a drop-in replacement for common Hadolint workflows. Existing inline suppression comments work without modification: ```dockerfile theme={null} # hadolint ignore=DL3006 FROM ubuntu # hadolint ignore=DL3004,DL3027 RUN apt install curl ``` Both `ignore=DL3006` and `ignore=hadolint/DL3006` are valid. You can also use tally's own directive format: ```dockerfile theme={null} # tally ignore=hadolint/DL3006 FROM ubuntu ``` ### Shell directive When using base images with non-POSIX shells (e.g., Windows images with PowerShell), declare the shell to disable POSIX-specific rules: ```dockerfile theme={null} FROM mcr.microsoft.com/windows/servercore:ltsc2022 # hadolint shell=powershell RUN Get-Process notepad | Stop-Process ``` Supported non-POSIX shells: `powershell`, `pwsh`, `cmd` / `cmd.exe`. When a non-POSIX shell is declared, tally automatically disables shell command analysis rules (e.g., DL3004 sudo detection, DL4001 wget/curl detection) and future ShellCheck-based rules. Both `# hadolint shell=` and `# tally shell=` formats are supported. *** ## ShellCheck rules (SC rules) ShellCheck rules analyze shell scripts within `RUN` commands. Implementation has started with native Go reimplementations that use tally's fix and reporting infrastructure. | Rule | Description | Status | | ------------------------------ | -------------------------------------------------------- | -------------- | | [SC1040](../shellcheck/SC1040) | `<<-` heredoc terminators may only be indented with tabs | Implemented 🔧 | Additional SC1xxx (syntax/parsing) and SC2xxx (logic/correctness) rules are planned. # Rules overview Source: https://tally.wharflab.com/rules/overview tally rules span four namespaces: tally/, buildkit/, hadolint/, and shellcheck/. Learn how to enable, disable, and suppress rules. tally integrates rules from multiple sources. Each rule belongs to a namespace that indicates its origin, and all rules share a common configuration and suppression model. ## Rule namespaces | Namespace | Source | Description | | ------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | `tally/` | tally custom rules | Security, correctness, performance, style, GPU, PHP, PowerShell, and Windows | | `buildkit/` | [Docker's BuildKit linter](https://docs.docker.com/reference/build-checks/) | Captured during parsing or reimplemented for static analysis | | `hadolint/` | [Hadolint](https://github.com/hadolint/hadolint) | Hadolint-compatible rules implemented natively | | `shellcheck/` | Embedded ShellCheck | Shell script analysis within `RUN` instructions | ## Severity levels | Severity | Meaning | | --------- | -------------------------------------------- | | `error` | Critical issue; blocks CI by default | | `warning` | Important issue that should be addressed | | `info` | Informational suggestion | | `style` | Style preference; auto-fixable in most cases | | `off` | Rule disabled | The default fail level is `style`, meaning any violation causes a non-zero exit. Use `--fail-level` to adjust this. ## Auto-fixable rules Rules marked with 🔧 can be fixed automatically with `tally lint --fix`. Some fixes are classified as suggestions (unsafe) and require `--fix --fix-unsafe` to apply. Auto-fixable rules cover formatting, style normalization, and many correctness improvements. ## Enabling and disabling rules ### In `.tally.toml` Use `include` and `exclude` glob patterns to select which rules run: ```toml theme={null} [rules] # Enable entire namespaces include = ["buildkit/*", "tally/*", "hadolint/*"] # Disable specific rules exclude = [ "buildkit/MaintainerDeprecated", "hadolint/DL3008", ] ``` Configure individual rules with `[rules..]`: ```toml theme={null} [rules.tally.max-lines] severity = "warning" max = 100 skip-blank-lines = true skip-comments = true [rules.buildkit.StageNameCasing] severity = "info" [rules.hadolint.DL3026] severity = "warning" trusted-registries = ["docker.io", "gcr.io", "ghcr.io"] ``` Rules that are off by default (such as `hadolint/DL3026`) are automatically enabled with `severity = "warning"` when you provide configuration options for them — no need to set `severity` explicitly unless you want a different level. ### With CLI flags Use `--select` to enable rules and `--ignore` to disable them: ```bash theme={null} # Enable only buildkit rules tally lint --select "buildkit/*" Dockerfile # Disable a specific rule tally lint --ignore "buildkit/MaintainerDeprecated" Dockerfile ``` ## Inline suppression directives Suppress specific violations directly in your Dockerfile using comment directives. ### Next-line suppression ```dockerfile theme={null} # tally ignore=StageNameCasing FROM alpine AS Build # tally ignore=DL3006,DL3007 FROM ubuntu:16.04 ``` ### File-wide suppression ```dockerfile theme={null} # tally global ignore=max-lines;reason=Generated file, size is expected FROM alpine ``` ### Adding a reason Use `;reason=` to document why a rule is suppressed. Required when `--require-reason` is set: ```dockerfile theme={null} # tally ignore=DL3006;reason=Using older base image for compatibility FROM ubuntu:16.04 ``` ### Suppress all rules on a line ```dockerfile theme={null} # tally ignore=all FROM Ubuntu AS Build ``` ### Migration compatibility tally also accepts directive formats from hadolint and Docker's `check=skip` syntax: ```dockerfile theme={null} # hadolint ignore=DL3024 FROM alpine AS builder # check=skip=StageNameCasing FROM alpine AS Builder ``` Directives work with or without namespace prefixes. Both `ignore=DL3024` and `ignore=hadolint/DL3024` are valid. ### Shell directive for non-POSIX shells When using a non-POSIX shell (PowerShell, cmd), use the `shell` directive to disable incompatible rules: ```dockerfile theme={null} FROM mcr.microsoft.com/windows/servercore:ltsc2022 # hadolint shell=powershell RUN Get-Process notepad | Stop-Process ``` Supported values: `powershell`, `pwsh`, `cmd`, `cmd.exe`. ## Explore rules by category Secret detection, VEX attestations, secret mounts, privilege rules, and telemetry opt-out. Stage structure, signal handling, JSON exec-form, identity resolution, curl/wget config, and platform checks. Multi-stage builds, cache mounts, heredocs, and archive extraction. Formatting, sorting, indentation, and epilogue ordering — all auto-fixable. Image metadata key validation, duplicate detection, Buildx overlap checks, base digest checks, and Docker namespace guardrails. NVIDIA/CUDA-aware rules for build-time queries, driver capabilities, and image size. Node and JavaScript container rules for native addon build caches. Composer dependency hygiene and Xdebug detection. Windows container-specific rules for mounts, signals, and ownership flags. Docker's official BuildKit linter checks. Hadolint DL rules implemented natively. # powershell/PSAlignAssignmentStatement Source: https://tally.wharflab.com/rules/powershell/PSAlignAssignmentStatement Align assignment statement `powershell/PSAlignAssignmentStatement` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Consecutive assignment statements are more readable when they're aligned. Assignments are considered aligned when their `equals` signs line up vertically. This rule looks at the key-value pairs in hashtables (including DSC configurations) as well as enum definitions. Consider the following example with a hashtable and enum that isn't aligned. ```powershell theme={null} $hashtable = @{ property = 'value' anotherProperty = 'another value' } enum Enum { member = 1 anotherMember = 2 } ``` Alignment in this case would look like the following. ```powershell theme={null} $hashtable = @{ property = 'value' anotherProperty = 'another value' } enum Enum { member = 1 anotherMember = 2 } ``` The rule ignores any assignments within hashtables and enums which are on the same line as others. For example, the rule ignores `$h = @{a = 1; b = 2}`. ## Configuration ```powershell theme={null} Rules = @{ PSAlignAssignmentStatement = @{ Enable = $true CheckHashtable = $true AlignHashtableKvpWithInterveningComment = $true CheckEnum = $true AlignEnumMemberWithInterveningComment = $true IncludeValuelessEnumMembers = $true } } ``` ### Parameters #### Enable: bool (Default value is `$false`) Enable or disable the rule during ScriptAnalyzer invocation. #### CheckHashtable: bool (Default value is `$true`) Enforce alignment of assignment statements in a hashtable and in a DSC Configuration. There is only one setting for hashtable and DSC configuration because the property value pairs in a DSC configuration are parsed as key-value pairs of a hashtable. #### AlignHashtableKvpWithInterveningComment: bool (Default value is `$true`) Include key-value pairs in the alignment that have an intervening comment - that is to say a comment between the key name and the equals sign. Consider the following: ```powershell theme={null} $hashtable = @{ property = 'value' anotherProperty <#A Comment#> = 'another value' anotherDifferentProperty = 'yet another value' } ``` With this setting disabled, the line with the comment is ignored, and it would be aligned like so: ```powershell theme={null} $hashtable = @{ property = 'value' anotherProperty <#A Comment#> = 'another value' anotherDifferentProperty = 'yet another value' } ``` With it enabled, the comment line is included in alignment: ```powershell theme={null} $hashtable = @{ property = 'value' anotherProperty <#A Comment#> = 'another value' anotherDifferentProperty = 'yet another value' } ``` #### CheckEnum: bool (Default value is `$true`) Enforce alignment of assignment statements of an Enum definition. #### AlignEnumMemberWithInterveningComment: bool (Default value is `$true`) Include enum members in the alignment that have an intervening comment - that is to say a comment between the member name and the equals sign. Consider the following: ```powershell theme={null} enum Enum { member = 1 anotherMember <#A Comment#> = 2 anotherDifferentMember = 3 } ``` With this setting disabled, the line with the comment is ignored, and it would be aligned like so: ```powershell theme={null} enum Enum { member = 1 anotherMember <#A Comment#> = 2 anotherDifferentMember = 3 } ``` With it enabled, the comment line is included in alignment: ```powershell theme={null} enum Enum { member = 1 anotherMember <#A Comment#> = 2 anotherDifferentMember = 3 } ``` #### IncludeValuelessEnumMembers: bool (Default value is `$true`) Include enum members in the alignment that don't have an explicitly assigned value. Enum's don't need to be given a value when they're defined. Consider the following: ```powershell theme={null} enum Enum { member = 1 anotherMember = 2 anotherDifferentMember } ``` With this setting disabled, the third line, which has no value, isn't considered when choosing where to align assignments. It would be aligned like so: ```powershell theme={null} enum Enum { member = 1 anotherMember = 2 anotherDifferentMember } ``` With it enabled, the valueless member is included in alignment as if it had a value: ```powershell theme={null} enum Enum { member = 1 anotherMember = 2 anotherDifferentMember } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AlignAssignmentStatement](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AlignAssignmentStatement.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidAssignmentToAutomaticVariable Source: https://tally.wharflab.com/rules/powershell/PSAvoidAssignmentToAutomaticVariable Changing automatic variables might have undesired side effects `powershell/PSAvoidAssignmentToAutomaticVariable` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description PowerShell has built-in variables known as automatic variables. Many of them are read-only and PowerShell throws an error when trying to assign a value on those. Other automatic variables should only be assigned in certain special cases to achieve a certain effect as a special technique. To understand more about automatic variables, see `Get-Help about_Automatic_Variables`. ## How Use variable names in functions or their parameters that do not conflict with automatic variables. ## Examples ### Problematic code The variable `$Error` is an automatic variable that exists in the global scope and should therefore never be used as a variable or parameter name. ```powershell theme={null} function foo($Error){ } ``` ```powershell theme={null} function Get-CustomErrorMessage($ErrorMessage){ $Error = "Error occurred: $ErrorMessage" } ``` ### Correct code ```powershell theme={null} function Get-CustomErrorMessage($ErrorMessage){ $FinalErrorMessage = "Error occurred: $ErrorMessage" } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidAssignmentToAutomaticVariable](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidAssignmentToAutomaticVariable.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidDefaultValueForMandatoryParameter Source: https://tally.wharflab.com/rules/powershell/PSAvoidDefaultValueForMandatoryParameter Avoid Default Value For Mandatory Parameter `powershell/PSAvoidDefaultValueForMandatoryParameter` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSAvoidDefaultValueForMandatoryParameter"]` or by setting `rules.powershell.PSAvoidDefaultValueForMandatoryParameter.severity = "warning"` in `.tally.toml`. ## Description Mandatory parameters should not have a default value because there is no scenario where the default can be used. PowerShell prompts for a value if the parameter value is not specified when calling the function. ## Examples ### Problematic code ```powershell theme={null} function Test { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] $Parameter1 = 'default Value' ) } ``` ### Correct code ```powershell theme={null} function Test { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] $Parameter1 ) } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidDefaultValueForMandatoryParameter](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidDefaultValueForMandatoryParameter.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidDefaultValueSwitchParameter Source: https://tally.wharflab.com/rules/powershell/PSAvoidDefaultValueSwitchParameter Switch Parameters Should Not Default To True `powershell/PSAvoidDefaultValueSwitchParameter` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSAvoidDefaultValueSwitchParameter"]` or by setting `rules.powershell.PSAvoidDefaultValueSwitchParameter.severity = "warning"` in `.tally.toml`. ## Description If your parameter takes only `true` and `false`, define the parameter as type `[Switch]`. PowerShell treats a switch parameter as `true` when it's used with a command. If the parameter isn't included with the command, PowerShell considers the parameter to be false. Don't define `[Boolean]` parameters. You shouldn't define a switch parameter with a default value of `$true` because this isn't the expected behavior of a switch parameter. ## How Change the default value of the switch parameter to be `$false` or don't provide a default value. Write the logic of the script to assume that the switch parameter default value is `$false` or not provided. ## Examples ### Problematic code ```powershell theme={null} function Test-Script { [CmdletBinding()] Param ( [String] $Param1, [switch] $Switch=$True ) ... } ``` ### Correct code ```powershell theme={null} function Test-Script { [CmdletBinding()] Param ( [String] $Param1, [switch] $Switch ) begin { # Ensure that the $Switch is set to false if not provided if (-not $PSBoundParameters.ContainsKey('Switch')) { $Switch = $false } } ... } ``` ## More information * [Strongly Encouraged Development Guidelines][01] [01]: https://learn.microsoft.com/powershell/scripting/developer/cmdlet/strongly-encouraged-development-guidelines#parameters-that-take-true-and-false ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidDefaultValueSwitchParameter](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidDefaultValueSwitchParameter.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidExclaimOperator Source: https://tally.wharflab.com/rules/powershell/PSAvoidExclaimOperator Avoid exclaim operator `powershell/PSAvoidExclaimOperator` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Avoid using the negation operator (`!`). Use `-not` for improved readability. Upstream PSScriptAnalyzer does not enable this rule by default. Configure `rules.powershell.PSAvoidExclaimOperator.Enable = true` to forward the upstream setting to PSScriptAnalyzer. ## How to Fix Replace the `!` negation operator with the PowerShell `-not` operator. ## Examples ### Problematic code ```powershell theme={null} $MyVar = !$true ``` ### Correct code ```powershell theme={null} $MyVar = -not $true ``` ## Configuration Upstream PSScriptAnalyzer supports the following rule setting. tally forwards matching `rules.powershell.PSAvoidExclaimOperator` options to PSScriptAnalyzer. ```powershell theme={null} Rules = @{ PSAvoidExclaimOperator = @{ Enable = $true } } ``` ### Parameters * `Enable`: **bool** (Default value is `$false`) Enable or disable the rule during ScriptAnalyzer invocation. ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidExclaimOperator](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidExclaimOperator.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidGlobalAliases Source: https://tally.wharflab.com/rules/powershell/PSAvoidGlobalAliases Avoid global aliases. `powershell/PSAvoidGlobalAliases` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSAvoidGlobalAliases"]` or by setting `rules.powershell.PSAvoidGlobalAliases.severity = "warning"` in `.tally.toml`. ## Description Globally scoped aliases override existing aliases in the session with matching names, which can cause difficult-to-debug issues for consumers of modules and scripts. To understand more about scoping, see `Get-Help about_Scopes`. **NOTE** This rule is not available in PowerShell version 3 or 4 because it uses the `StaticParameterBinder.BindCommand` API. ## How Use other scope modifiers for new aliases. ## Examples ### Problematic code ```powershell theme={null} New-Alias -Name Name -Value Value -Scope Global ``` ### Correct code ```powershell theme={null} New-Alias -Name Name1 -Value Value ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidGlobalAliases](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidGlobalAliases.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidGlobalFunctions Source: https://tally.wharflab.com/rules/powershell/PSAvoidGlobalFunctions Avoid global functions and aliases `powershell/PSAvoidGlobalFunctions` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSAvoidGlobalFunctions"]` or by setting `rules.powershell.PSAvoidGlobalFunctions.severity = "warning"` in `.tally.toml`. ## Description Globally scoped functions override existing functions within the sessions with matching names. This name collision can cause difficult-to-debug issues for consumers of modules. To understand more about scoping, see `Get-Help about_Scopes`. ## How Use other scope modifiers for functions. ## Examples ### Problematic code ```powershell theme={null} function global:functionName {} ``` ### Correct code ```powershell theme={null} function functionName {} ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidGlobalFunctions](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidGlobalFunctions.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidGlobalVars Source: https://tally.wharflab.com/rules/powershell/PSAvoidGlobalVars No Global Variables `powershell/PSAvoidGlobalVars` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSAvoidGlobalVars"]` or by setting `rules.powershell.PSAvoidGlobalVars.severity = "warning"` in `.tally.toml`. ## Description A variable is a unit of memory in which values are stored. PowerShell controls access to variables, functions, aliases, and drives through a mechanism known as scoping. Variables and functions that are present when PowerShell starts have been created in the global scope. Globally scoped variables include: * Automatic variables * Preference variables * Variables, aliases, and functions that are in your PowerShell profiles To understand more about scoping, see `Get-Help about_Scopes`. ## How Use other scope modifiers for variables. ## Examples ### Problematic code ```powershell theme={null} $Global:var1 = $null function Test-NotGlobal ($var) { $a = $var + $var1 } ``` ### Correct code ```powershell theme={null} $var1 = $null function Test-NotGlobal ($var1, $var2) { $a = $var1 + $var2 } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidGlobalVars](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidGlobalVars.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidInvokingEmptyMembers Source: https://tally.wharflab.com/rules/powershell/PSAvoidInvokingEmptyMembers Avoid Invoking Empty Members `powershell/PSAvoidInvokingEmptyMembers` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Invoking non-constant members can cause potential bugs. Please double-check the syntax to make sure that invoked members are constants. ## How Provide the requested members for a given type or class. ## Examples ### Problematic code ```powershell theme={null} $MyString = 'abc' $MyString.('len'+'gth') ``` ### Correct code ```powershell theme={null} $MyString = 'abc' $MyString.('length') ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidInvokingEmptyMembers](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidInvokingEmptyMembers.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidLongLines Source: https://tally.wharflab.com/rules/powershell/PSAvoidLongLines Avoid long lines `powershell/PSAvoidLongLines` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description The length of lines, including leading spaces (indentation), should be less than the configured number of characters. The default length is 120 characters. This rule isn't enabled by default. The user needs to enable it through settings. ## Configuration ```powershell theme={null} Rules = @{ PSAvoidLongLines = @{ Enable = $true MaximumLineLength = 120 } } ``` ## Parameters ### `Enable`: bool (Default value is `$false`) Enable or disable the rule during ScriptAnalyzer invocation. ### `MaximumLineLength`: int (Default value is 120) Optional parameter to override the default maximum line length. ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidLongLines](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidLongLines.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidMultipleTypeAttributes Source: https://tally.wharflab.com/rules/powershell/PSAvoidMultipleTypeAttributes Avoid multiple type specifiers on parameters. `powershell/PSAvoidMultipleTypeAttributes` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Parameters should not have more than one type specifier. Multiple type specifiers on parameters can cause runtime errors. ## How Ensure each parameter has only 1 type specifier. ## Examples ### Problematic code ```powershell theme={null} function Test-Script { [CmdletBinding()] Param ( [switch] [int] $Switch ) } ``` ### Correct code ```powershell theme={null} function Test-Script { [CmdletBinding()] Param ( [switch] $Switch ) } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidMultipleTypeAttributes](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidMultipleTypeAttributes.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidNullOrEmptyHelpMessageAttribute Source: https://tally.wharflab.com/rules/powershell/PSAvoidNullOrEmptyHelpMessageAttribute Avoid using null or empty HelpMessage parameter attribute. `powershell/PSAvoidNullOrEmptyHelpMessageAttribute` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSAvoidNullOrEmptyHelpMessageAttribute"]` or by setting `rules.powershell.PSAvoidNullOrEmptyHelpMessageAttribute.severity = "warning"` in `.tally.toml`. ## Description The value of the `HelpMessage` attribute should not be an empty string or a null value as this causes PowerShell's interpreter to throw an error when executing the function or cmdlet. ## How Specify a value for the `HelpMessage` attribute. ## Examples ### Problematic code ```powershell theme={null} Function BadFuncEmptyHelpMessageEmpty { Param( [Parameter(HelpMessage='')] [String] $Param ) $Param } Function BadFuncEmptyHelpMessageNull { Param( [Parameter(HelpMessage=$null)] [String] $Param ) $Param } Function BadFuncEmptyHelpMessageNoAssignment { Param( [Parameter(HelpMessage)] [String] $Param ) $Param } ``` ### Correct code ```powershell theme={null} Function GoodFuncHelpMessage { Param( [Parameter(HelpMessage='This is helpful')] [String] $Param ) $Param } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidNullOrEmptyHelpMessageAttribute](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidNullOrEmptyHelpMessageAttribute.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidOverwritingBuiltInCmdlets Source: https://tally.wharflab.com/rules/powershell/PSAvoidOverwritingBuiltInCmdlets Avoid overwriting built in cmdlets `powershell/PSAvoidOverwritingBuiltInCmdlets` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description This rule flags cmdlets that are available in a given edition/version of PowerShell on a given operating system which are overwritten by a function declaration. It works by comparing function declarations against a set of allowlists that ship with PSScriptAnalyzer. These allowlist files are used by other PSScriptAnalyzer rules. More information can be found in the documentation for the [UseCompatibleCmdlets][01] rule. ## Configuration To enable the rule to check if your script is compatible on PowerShell Core on Windows, put the following in your settings file. tally forwards matching `rules.powershell.PSAvoidOverwritingBuiltInCmdlets` options to PSScriptAnalyzer. ```powershell theme={null} @{ 'Rules' = @{ 'PSAvoidOverwritingBuiltInCmdlets' = @{ 'PowerShellVersion' = @('core-6.1.0-windows') } } } ``` ### Parameters #### PowerShellVersion The parameter `PowerShellVersion` is a list of allowlists that ship with PSScriptAnalyzer. In upstream PSScriptAnalyzer, the default value for `PowerShellVersion` is `core-6.1.0-windows` if PowerShell 6 or later is installed, and `desktop-5.1.14393.206-windows` if it is not. tally itself requires `pwsh` 7.x as the analyzer host and does not support `powershell.exe` or Windows PowerShell 5.1 as the host process. Usually, patched versions of PowerShell have the same cmdlet data, therefore only settings of major and minor versions of PowerShell are supplied. One can also create a custom settings file as well with the [New-CommandDataFile.ps1][02] script and use it by placing the created `JSON` into the `Settings` folder of the `PSScriptAnalyzer` module installation folder, then the `PowerShellVersion` parameter is just its filename (that can also be changed if desired). Note that the `core-6.0.2-*` files were removed in PSScriptAnalyzer 1.18 since PowerShell 6.0 reached end of life. [01]: /rules/powershell/PSUseCompatibleCmdlets [02]: https://github.com/PowerShell/PSScriptAnalyzer/blob/main/Utils/New-CommandDataFile.ps1 ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidOverwritingBuiltInCmdlets](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidOverwritingBuiltInCmdlets.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidReservedWordsAsFunctionNames Source: https://tally.wharflab.com/rules/powershell/PSAvoidReservedWordsAsFunctionNames Avoid reserved words as function names `powershell/PSAvoidReservedWordsAsFunctionNames` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Avoid using reserved words as function names. Using reserved words as function names can cause errors or unexpected behavior in scripts. ## How to Fix Avoid using any of the reserved words as function names. Choose a different name that's not a reserved word. See [about\_Reserved\_Words][01] for a list of reserved words in PowerShell. ## Examples ### Problematic code ```powershell theme={null} # Function is a reserved word function function { Write-Host "Hello, World!" } ``` ### Correct code ```powershell theme={null} # myFunction is not a reserved word function myFunction { Write-Host "Hello, World!" } ``` [01]: /powershell/module/microsoft.powershell.core/about/about_reserved_words ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidReservedWordsAsFunctionNames](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidReservedWordsAsFunctionNames.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidSemicolonsAsLineTerminators Source: https://tally.wharflab.com/rules/powershell/PSAvoidSemicolonsAsLineTerminators Avoid semicolons as line terminators `powershell/PSAvoidSemicolonsAsLineTerminators` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Lines should not end with a semicolon. This rule is not enabled by default. The user needs to enable it through settings. ## Examples ### Problematic code ```powershell theme={null} Install-Module -Name PSScriptAnalyzer; $a = 1 + $b; ``` ```powershell theme={null} Install-Module -Name PSScriptAnalyzer; $a = 1 + $b ``` ### Correct code ```powershell theme={null} Install-Module -Name PSScriptAnalyzer; $a = 1 + $b ``` ```powershell theme={null} Install-Module -Name PSScriptAnalyzer $a = 1 + $b ``` ## Configuration ```powershell theme={null} Rules = @{ PSAvoidSemicolonsAsLineTerminators = @{ Enable = $true } } ``` ### Parameters #### Enable: bool (Default value is `$false`) Enable or disable the rule during ScriptAnalyzer invocation. ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidSemicolonsAsLineTerminators](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidSemicolonsAsLineTerminators.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidShouldContinueWithoutForce Source: https://tally.wharflab.com/rules/powershell/PSAvoidShouldContinueWithoutForce Avoid Using ShouldContinue Without Boolean Force Parameter `powershell/PSAvoidShouldContinueWithoutForce` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSAvoidShouldContinueWithoutForce"]` or by setting `rules.powershell.PSAvoidShouldContinueWithoutForce.severity = "warning"` in `.tally.toml`. ## Description Functions that use ShouldContinue should have a boolean force parameter to allow user to bypass it. You can get more details by running `Get-Help about_Functions_CmdletBindingAttribute` and `Get-Help about_Functions_Advanced_Methods` command in PowerShell. ## How Call the `ShouldContinue` method in advanced functions when `ShouldProcess` method returns `$true`. ## Examples ### Problematic code ```powershell theme={null} Function Test-ShouldContinue { [CmdletBinding(SupportsShouldProcess=$true)] Param ( $MyString = 'blah' ) if ($PsCmdlet.ShouldContinue('ShouldContinue Query', 'ShouldContinue Caption')) { ... } } ``` ### Correct code ```powershell theme={null} Function Test-ShouldContinue { [CmdletBinding(SupportsShouldProcess=$true)] Param ( $MyString = 'blah', [Switch]$Force ) if ($Force -or $PsCmdlet.ShouldContinue('ShouldContinue Query', 'ShouldContinue Caption')) { ... } } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidShouldContinueWithoutForce](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidShouldContinueWithoutForce.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidTrailingWhitespace Source: https://tally.wharflab.com/rules/powershell/PSAvoidTrailingWhitespace Avoid trailing whitespace `powershell/PSAvoidTrailingWhitespace` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Information | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Lines should not end with whitespace characters. This can cause problems with the line-continuation backtick, and also clutters up future commits to source control. This diagnostic can coexist with [`tally/no-trailing-spaces`](/rules/tally/no-trailing-spaces): PSScriptAnalyzer reports trailing whitespace in PowerShell snippets, while tally checks the Dockerfile as a whole. If both rules suggest the same edit, tally applies it once. ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidTrailingWhitespace](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidTrailingWhitespace.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidUsingAllowUnencryptedAuthentication Source: https://tally.wharflab.com/rules/powershell/PSAvoidUsingAllowUnencryptedAuthentication Avoid sending credentials and secrets over unencrypted connections `powershell/PSAvoidUsingAllowUnencryptedAuthentication` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Avoid using the **AllowUnencryptedAuthentication** parameter of `Invoke-WebRequest` and `Invoke-RestMethod`. When using this parameter, the cmdlets send credentials and secrets over unencrypted connections. This should be avoided except for compatibility with legacy systems. For more details, see [Invoke-RestMethod](https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/invoke-restmethod). ## How Avoid using the **AllowUnencryptedAuthentication** parameter. ## Example 1 ### Problematic code ```powershell theme={null} Invoke-WebRequest foo -AllowUnencryptedAuthentication ``` ### Correct code ```powershell theme={null} Invoke-WebRequest foo ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidUsingAllowUnencryptedAuthentication](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidUsingAllowUnencryptedAuthentication.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidUsingBrokenHashAlgorithms Source: https://tally.wharflab.com/rules/powershell/PSAvoidUsingBrokenHashAlgorithms Avoid using broken hash algorithms `powershell/PSAvoidUsingBrokenHashAlgorithms` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Avoid using the broken algorithms MD5 or SHA-1. ## How Replace broken algorithms with secure alternatives. MD5 and SHA-1 should be replaced with SHA256, SHA384, SHA512, or other safer algorithms when possible, with MD5 and SHA-1 only being utilized by necessity for backwards compatibility. ## Example 1 ### Problematic code ```powershell theme={null} Get-FileHash foo.txt -Algorithm MD5 ``` ### Correct code ```powershell theme={null} Get-FileHash foo.txt -Algorithm SHA256 ``` ## Example 2 ### Problematic code ```powershell theme={null} Get-FileHash foo.txt -Algorithm SHA1 ``` ### Correct code ```powershell theme={null} Get-FileHash foo.txt ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidUsingBrokenHashAlgorithms](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidUsingBrokenHashAlgorithms.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidUsingCmdletAliases Source: https://tally.wharflab.com/rules/powershell/PSAvoidUsingCmdletAliases Avoid Using Cmdlet Aliases or omitting the 'Get-' prefix. `powershell/PSAvoidUsingCmdletAliases` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description An alias is an alternate name or nickname for a cmdlet or for a command element, such as a function, script, file, or executable file. You can use the alias instead of the command name in any PowerShell commands. There are also implicit aliases. When PowerShell cannot find the cmdlet name, it will try to append `Get-` to the command as a last resort. Therefore using the command `verb` will execute `Get-Verb`. Every PowerShell author learns the actual command names, but different authors learn and use different aliases. Aliases can make code difficult to read, understand and impact availability. Using the full command name makes it easier to maintain your scripts in the future. Using the full command names also allows for syntax highlighting in sites and applications like GitHub and Visual Studio Code. ## How to Fix Use the full cmdlet name and not an alias. ## Alias Allowlist To prevent `PSScriptAnalyzer` from flagging your preferred aliases, create an allowlist of the aliases in your settings file and point `PSScriptAnalyzer` to use the settings file. For example, to disable `PSScriptAnalyzer` from flagging `cd`, which is an alias of `Set-Location`, set the settings file content to the following. ```powershell theme={null} # PSScriptAnalyzerSettings.psd1 @{ 'Rules' = @{ 'PSAvoidUsingCmdletAliases' = @{ 'allowlist' = @('cd') } } } ``` ## Examples ### Problematic code ```powershell theme={null} gps | Where-Object {$_.WorkingSet -gt 20000000} ``` ### Correct code ```powershell theme={null} Get-Process | Where-Object {$_.WorkingSet -gt 20000000} ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidUsingCmdletAliases](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidUsingCmdletAliases.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidUsingComputerNameHardcoded Source: https://tally.wharflab.com/rules/powershell/PSAvoidUsingComputerNameHardcoded Avoid Using ComputerName Hardcoded `powershell/PSAvoidUsingComputerNameHardcoded` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Error | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description The names of computers should never be hard coded as this will expose sensitive information. The `ComputerName` parameter should never have a hard coded value. ## How Remove hard coded computer names. ## Example 1 ### Problematic code ```powershell theme={null} Function Invoke-MyRemoteCommand () { Invoke-Command -Port 343 -ComputerName hardcoderemotehostname } ``` ### Correct code ```powershell theme={null} Function Invoke-MyCommand ($ComputerName) { Invoke-Command -Port 343 -ComputerName $ComputerName } ``` ## Example 2 ### Problematic code ```powershell theme={null} Function Invoke-MyLocalCommand () { Invoke-Command -Port 343 -ComputerName hardcodelocalhostname } ``` ### Correct code ```powershell theme={null} Function Invoke-MyLocalCommand () { Invoke-Command -Port 343 -ComputerName $env:COMPUTERNAME } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidUsingComputerNameHardcoded](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidUsingComputerNameHardcoded.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidUsingConvertToSecureStringWithPlainText Source: https://tally.wharflab.com/rules/powershell/PSAvoidUsingConvertToSecureStringWithPlainText Avoid Using SecureString With Plain Text `powershell/PSAvoidUsingConvertToSecureStringWithPlainText` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Error | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description The use of the `AsPlainText` parameter with the `ConvertTo-SecureString` command can expose secure information. ## How Use a standard encrypted variable to perform any SecureString conversions. ## Recommendations If you do need an ability to retrieve the password from somewhere without prompting the user, consider using the [SecretStore](https://www.powershellgallery.com/packages/Microsoft.PowerShell.SecretStore) module from the PowerShell Gallery. ## Examples ### Problematic code ```powershell theme={null} $UserInput = Read-Host 'Please enter your secure code' $EncryptedInput = ConvertTo-SecureString -String $UserInput -AsPlainText -Force ``` ### Correct code ```powershell theme={null} $SecureUserInput = Read-Host 'Please enter your secure code' -AsSecureString ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidUsingConvertToSecureStringWithPlainText](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidUsingConvertToSecureStringWithPlainText.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidUsingDeprecatedManifestFields Source: https://tally.wharflab.com/rules/powershell/PSAvoidUsingDeprecatedManifestFields Avoid Using Deprecated Manifest Fields `powershell/PSAvoidUsingDeprecatedManifestFields` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default because Dockerfiles do not ship PowerShell module manifests (`.psd1` files) — this rule targets module-authoring artifacts that are out of scope for container builds. Re-enable it with `include = ["powershell/PSAvoidUsingDeprecatedManifestFields"]` or by setting `rules.powershell.PSAvoidUsingDeprecatedManifestFields.severity = "warning"` in `.tally.toml`. ## Description In PowerShell 5.0, a number of fields in module manifest files (`.psd1`) have been changed. The field `ModuleToProcess` has been replaced with the `RootModule` field. ## How Replace `ModuleToProcess` with `RootModule` in the module manifest. ## Examples ### Problematic code ```powershell theme={null} ModuleToProcess ='psscriptanalyzer' ModuleVersion = '1.0' ``` ### Correct code ```powershell theme={null} RootModule ='psscriptanalyzer' ModuleVersion = '1.0' ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidUsingDeprecatedManifestFields](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidUsingDeprecatedManifestFields.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidUsingDoubleQuotesForConstantString Source: https://tally.wharflab.com/rules/powershell/PSAvoidUsingDoubleQuotesForConstantString Avoid using double quotes if the string is constant. `powershell/PSAvoidUsingDoubleQuotesForConstantString` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Information | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Single quotes should be used when the value of a string is constant. A constant string doesn't contain variables or expressions intended to insert values into the string, such as `"$PID-$(hostname)"`). This makes the intent clearer that the string is a constant and makes it easier to use some special characters such as `$` within that string expression without needing to escape them. There are exceptions to that when double quoted strings are more readable. For example, when the string value itself must contain a single quote or other special characters, such as newline (``"`n"``), are already being escaped. The rule does not warn in these cases. ## Examples ### Problematic code ```powershell theme={null} $constantValue = "I Love PowerShell" ``` ### Correct code ```powershell theme={null} $constantValue = 'I Love PowerShell' ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidUsingDoubleQuotesForConstantString](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidUsingDoubleQuotesForConstantString.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidUsingEmptyCatchBlock Source: https://tally.wharflab.com/rules/powershell/PSAvoidUsingEmptyCatchBlock Avoid Using Empty Catch Block `powershell/PSAvoidUsingEmptyCatchBlock` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Empty catch blocks are considered a poor design choice because any errors occurring in a `try` block cannot be handled. ## How Use `Write-Error` or `throw` statements within the catch block. ## Examples ### Problematic code ```powershell theme={null} try { 1/0 } catch [DivideByZeroException] { } ``` ### Correct code ```powershell theme={null} try { 1/0 } catch [DivideByZeroException] { Write-Error 'DivideByZeroException' } try { 1/0 } catch [DivideByZeroException] { throw 'DivideByZeroException' } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidUsingEmptyCatchBlock](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidUsingEmptyCatchBlock.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidUsingInvokeExpression Source: https://tally.wharflab.com/rules/powershell/PSAvoidUsingInvokeExpression Avoid Using Invoke-Expression `powershell/PSAvoidUsingInvokeExpression` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Care must be taken when using the `Invoke-Expression` command. The `Invoke-Expression` executes the specified string and returns the results. Code injection into your application or script can occur if the expression passed as a string includes any data provided from the user. ## How Remove the use of `Invoke-Expression`. ## Examples ### Problematic code ```powershell theme={null} Invoke-Expression 'Get-Process' ``` ### Correct code ```powershell theme={null} Get-Process ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidUsingInvokeExpression](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidUsingInvokeExpression.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidUsingPlainTextForPassword Source: https://tally.wharflab.com/rules/powershell/PSAvoidUsingPlainTextForPassword Avoid Using Plain Text For Password Parameter `powershell/PSAvoidUsingPlainTextForPassword` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Password parameters that take in plaintext will expose passwords and compromise the security of your system. Passwords should be stored in the **SecureString** type. The following parameters are considered password parameters (this is not case sensitive): * Password * Pass * Passwords * Passphrase * Passphrases * PasswordParam If a parameter is defined with a name in the above list, it should be declared with type **SecureString**. ## How Change the type to **SecureString**. ## Examples ### Problematic code ```powershell theme={null} function Test-Script { [CmdletBinding()] Param ( [string] $Password ) ... } ``` ### Correct code ```powershell theme={null} function Test-Script { [CmdletBinding()] Param ( [SecureString] $Password ) ... } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidUsingPlainTextForPassword](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidUsingPlainTextForPassword.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidUsingPositionalParameters Source: https://tally.wharflab.com/rules/powershell/PSAvoidUsingPositionalParameters Avoid Using Positional Parameters `powershell/PSAvoidUsingPositionalParameters` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Information | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because there is no downstream pipeline or caller in a one-shot container build, so the script-reusability concerns this rule targets do not apply. Re-enable it with `include = ["powershell/PSAvoidUsingPositionalParameters"]` or by setting `rules.powershell.PSAvoidUsingPositionalParameters.severity = "warning"` in `.tally.toml`. ## Description Using positional parameters reduces the readability of code and can introduce errors. It is possible that a future version of the cmdlet could change in a way that would break existing scripts if calls to the cmdlet rely on the position of the parameters. For simple cmdlets with only a few positional parameters, the risk is much smaller. To prevent this rule from being too noisy, this rule gets only triggered when there are 3 or more parameters supplied. A simple example where the risk of using positional parameters is negligible, is `Test-Path $Path`. ## Configuration ```powershell theme={null} Rules = @{ PSAvoidUsingPositionalParameters = @{ CommandAllowList = 'Join-Path', 'MyCmdletOrScript' Enable = $true } } ``` ### Parameters #### CommandAllowList: string\[] (Default value is `@()`) Commands or scripts to be excluded from this rule. #### Enable: bool (Default value is `$true`) Enable or disable the rule during ScriptAnalyzer invocation. ## How Use full parameter names when calling commands. ## Examples ### Problematic code ```powershell theme={null} Get-Command ChildItem Microsoft.PowerShell.Management ``` ### Correct code ```powershell theme={null} Get-Command -Noun ChildItem -Module Microsoft.PowerShell.Management ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidUsingPositionalParameters](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidUsingPositionalParameters.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidUsingUsernameAndPasswordParams Source: https://tally.wharflab.com/rules/powershell/PSAvoidUsingUsernameAndPasswordParams Avoid Using Username and Password Parameters `powershell/PSAvoidUsingUsernameAndPasswordParams` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Error | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description To standardize command parameters, credentials should be accepted as objects of type **PSCredential**. Functions should not make use of username or password parameters. ## How Change the parameter to type **PSCredential**. ## Examples ### Problematic code ```powershell theme={null} function Test-Script { [CmdletBinding()] Param ( [String] $Username, [SecureString] $Password ) ... } ``` ### Correct code ```powershell theme={null} function Test-Script { [CmdletBinding()] Param ( [PSCredential] $Credential ) ... } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidUsingUsernameAndPasswordParams](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidUsingUsernameAndPasswordParams.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidUsingWMICmdlet Source: https://tally.wharflab.com/rules/powershell/PSAvoidUsingWMICmdlet Avoid Using Get-WMIObject, Remove-WMIObject, Invoke-WmiMethod, Register-WmiEvent, Set-WmiInstance `powershell/PSAvoidUsingWMICmdlet` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description As of PowerShell 3.0, the CIM cmdlets should be used over the WMI cmdlets. The following cmdlets should not be used: * `Get-WmiObject` * `Remove-WmiObject` * `Invoke-WmiMethod` * `Register-WmiEvent` * `Set-WmiInstance` Use the following cmdlets instead: * `Get-CimInstance` * `Remove-CimInstance` * `Invoke-CimMethod` * `Register-CimIndicationEvent` * `Set-CimInstance` The CIM cmdlets comply with WS-Management (WSMan) standards and with the Common Information Model (CIM) standard, allowing for the management of Windows and non-Windows operating systems. ## How Change to the equivalent CIM-based cmdlet. * `Get-WmiObject` -> `Get-CimInstance` * `Remove-WmiObject` -> `Remove-CimInstance` * `Invoke-WmiMethod` -> `Invoke-CimMethod` * `Register-WmiEvent` -> `Register-CimIndicationEvent` * `Set-WmiInstance` -> `Set-CimInstance` ## Examples ### Problematic code ```powershell theme={null} Get-WmiObject -Query 'Select * from Win32_Process where name LIKE "myprocess%"' | Remove-WmiObject Invoke-WmiMethod -Class Win32_Process -Name 'Create' -ArgumentList @{ CommandLine = 'notepad.exe' } ``` ### Correct code ```powershell theme={null} Get-CimInstance -Query 'Select * from Win32_Process where name LIKE "myprocess%"' | Remove-CIMInstance Invoke-CimMethod -ClassName Win32_Process -MethodName 'Create' -Arguments @{ CommandLine = 'notepad.exe' } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidUsingWMICmdlet](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidUsingWMICmdlet.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSAvoidUsingWriteHost Source: https://tally.wharflab.com/rules/powershell/PSAvoidUsingWriteHost Avoid Using Write-Host `powershell/PSAvoidUsingWriteHost` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because there is no downstream pipeline or interactive host inside a one-shot container build — the script-reusability concerns this rule targets do not apply. Re-enable it with `include = ["powershell/PSAvoidUsingWriteHost"]` or by setting `rules.powershell.PSAvoidUsingWriteHost.severity = "warning"` in `.tally.toml`. ## Description The primary purpose of the `Write-Host` cmdlet is to produce display-only output in the host. For example: printing colored text or prompting the user for input when combined with `Read-Host`. `Write-Host` uses the `ToString()` method to write the output. The particular result depends on the program that's hosting PowerShell. The output from `Write-Host` isn't sent to the pipeline. To output data to the pipeline, use `Write-Output` or implicit output. The use of `Write-Host` in a function is discouraged unless the function uses the `Show` verb. The `Show` verb explicitly means *display information to the user*. This rule doesn't apply to functions with the `Show` verb. ## How Replace `Write-Host` with `Write-Output` or `Write-Verbose` depending on whether the intention is logging or returning one or more objects. ## Examples ### Problematic code ```powershell theme={null} function Get-MeaningOfLife { Write-Host 'Computing the answer to the ultimate question of life, the universe and everything' Write-Host 42 } ``` ### Correct code Use `Write-Verbose` for informational messages. The user can decide whether to see the message by providing the **Verbose** parameter. ```powershell theme={null} function Get-MeaningOfLife { [CmdletBinding()]Param() # makes it possible to support Verbose output Write-Verbose 'Computing the answer to the ultimate question of life, the universe and everything' Write-Output 42 } function Show-Something { Write-Host 'show something on screen' } ``` ## More information [Write-Host](https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-host) ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [AvoidUsingWriteHost](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/AvoidUsingWriteHost.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSDSCDscExamplesPresent Source: https://tally.wharflab.com/rules/powershell/PSDSCDscExamplesPresent DSC examples are present `powershell/PSDSCDscExamplesPresent` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Information | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default because Desired State Configuration resources are out of scope for Dockerfile `RUN` analysis. Re-enable it with `include = ["powershell/PSDSCDscExamplesPresent"]` or by setting `rules.powershell.PSDSCDscExamplesPresent.severity = "warning"` in `.tally.toml`. ## Description Checks that DSC examples for given resource are present. ## How To fix a violation of this rule, please make sure `Examples` directory is present: * For non-class based resources it should exist at the same folder level as `DSCResources` folder. * For class based resources it should be present at the same folder level as resource `.psm1` file. The `Examples` folder should contain a sample configuration for given resource. The filename should contain the resource's name. ## Examples ### Non-class based resource Let's assume we have non-class based resource with a following file structure: * xAzure * DSCResources * MSFT\_xAzureSubscription * MSFT\_xAzureSubscription.psm1 * MSFT\_xAzureSubscription.schema.mof In this case, to fix this warning, we should add examples in a following way: * xAzure * DSCResources * MSFT\_xAzureSubscription * MSFT\_xAzureSubscription.psm1 * MSFT\_xAzureSubscription.schema.mof * Examples * MSFT\_xAzureSubscription\_AddSubscriptionExample.ps1 * MSFT\_xAzureSubscription\_RemoveSubscriptionExample.ps1 ### Class based resource Let's assume we have class based resource with a following file structure: * MyDscResource * MyDscResource.psm1 * MyDscResource.psd1 In this case, to fix this warning, we should add examples in a following way: * MyDscResource * MyDscResource.psm1 * MyDscResource.psd1 * Examples * MyDscResource\_Example1.ps1 * MyDscResource\_Example2.ps1 ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [DSCDscExamplesPresent](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/DSCDscExamplesPresent.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSDSCDscTestsPresent Source: https://tally.wharflab.com/rules/powershell/PSDSCDscTestsPresent Dsc tests are present `powershell/PSDSCDscTestsPresent` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Information | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default because Desired State Configuration resources are out of scope for Dockerfile `RUN` analysis. Re-enable it with `include = ["powershell/PSDSCDscTestsPresent"]` or by setting `rules.powershell.PSDSCDscTestsPresent.severity = "warning"` in `.tally.toml`. ## Description Checks that DSC tests for given resource are present. ## How To fix a violation of this rule, please make sure `Tests` directory is present: * For non-class-based resources, it should exist at the same folder level as the `DSCResources` folder. * For class-based resources, it should be present at the same folder level as the resource `.psm1` file. The `Tests` folder should contain a test script for a given resource. The filename should contain the resource's name. ## Examples ### Non-class-based resource Let's assume we have a non-class-based resource with the following file structure: * xAzure * DSCResources * MSFT\_xAzureSubscription * MSFT\_xAzureSubscription.psm1 * MSFT\_xAzureSubscription.schema.mof In this case, to fix this warning, we should add tests in a following way: * xAzure * DSCResources * MSFT\_xAzureSubscription * MSFT\_xAzureSubscription.psm1 * MSFT\_xAzureSubscription.schema.mof * Tests * MSFT\_xAzureSubscription\_Tests.ps1 ### Class-based resource Let's assume we have a class-based resource with the following file structure: * MyDscResource * MyDscResource.psm1 * MyDscResource.psd1 In this case, to fix this warning, we should add tests in a following way: * MyDscResource * MyDscResource.psm1 * MyDscResource.psd1 * Tests * MyDscResource\_Tests.ps1 ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [DSCDscTestsPresent](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/DSCDscTestsPresent.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSDSCReturnCorrectTypesForDSCFunctions Source: https://tally.wharflab.com/rules/powershell/PSDSCReturnCorrectTypesForDSCFunctions Return Correct Types For DSC Functions `powershell/PSDSCReturnCorrectTypesForDSCFunctions` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Information | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default because Desired State Configuration resources are out of scope for Dockerfile `RUN` analysis. Re-enable it with `include = ["powershell/PSDSCReturnCorrectTypesForDSCFunctions"]` or by setting `rules.powershell.PSDSCReturnCorrectTypesForDSCFunctions.severity = "warning"` in `.tally.toml`. ## Description The functions in DSC resources have specific return objects. For non-class based resources: * `Set-TargetResource` must not return any value. * `Test-TargetResource` must return a boolean. * `Get-TargetResource` must return a hash table. For class based resources: * `Set` must not return any value. * `Test` must return a boolean. * `Get` must return an instance of the DSC class. ## How Ensure that each function returns the correct type. ## Example 1 ### Problematic code ```powershell theme={null} function Get-TargetResource { param ( [parameter(Mandatory = $true)] [String] $Name ) ... } function Set-TargetResource { param ( [parameter(Mandatory = $true)] [String] $Name ) ... } function Test-TargetResource { param ( [parameter(Mandatory = $true)] [String] $Name ) ... } ``` ### Correct code ```powershell theme={null} function Get-TargetResource { [OutputType([Hashtable])] param ( [parameter(Mandatory = $true)] [String] $Name ) ... } function Set-TargetResource { param ( [parameter(Mandatory = $true)] [String] $Name ) ... } function Test-TargetResource { [OutputType([System.Boolean])] param ( [parameter(Mandatory = $true)] [String] $Name ) ... } ``` ## Example 2 ### Problematic code ```powershell theme={null} [DscResource()] class MyDSCResource { [DscProperty(Key)] [string] $Name [String] Get() { ... } [String] Set() { ... } [bool] Test() { ... } } ``` ### Correct code ```powershell theme={null} [DscResource()] class MyDSCResource { [DscProperty(Key)] [string] $Name [MyDSCResource] Get() { ... } [void] Set() { ... } [bool] Test() { ... } } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [DSCReturnCorrectTypesForDSCFunctions](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/DSCReturnCorrectTypesForDSCFunctions.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSDSCStandardDSCFunctionsInResource Source: https://tally.wharflab.com/rules/powershell/PSDSCStandardDSCFunctionsInResource Use Standard Get/Set/Test TargetResource functions in DSC Resource `powershell/PSDSCStandardDSCFunctionsInResource` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Error | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default because Desired State Configuration resources are out of scope for Dockerfile `RUN` analysis. Re-enable it with `include = ["powershell/PSDSCStandardDSCFunctionsInResource"]` or by setting `rules.powershell.PSDSCStandardDSCFunctionsInResource.severity = "warning"` in `.tally.toml`. ## Description All DSC resources are required to implement the correct functions. For non-class-based resources: * `Set-TargetResource` * `Test-TargetResource` * `Get-TargetResource` For class-based resources: * `Set` * `Test` * `Get` ## How Add the missing functions to the resource. ## Example 1 ### Problematic code ```powershell theme={null} function Get-TargetResource { [OutputType([Hashtable])] param ( [parameter(Mandatory = $true)] [String] $Name ) ... } function Set-TargetResource { param ( [parameter(Mandatory = $true)] [String] $Name ) ... } ``` ### Correct code ```powershell theme={null} function Get-TargetResource { [OutputType([Hashtable])] param ( [parameter(Mandatory = $true)] [String] $Name ) ... } function Set-TargetResource { param ( [parameter(Mandatory = $true)] [String] $Name ) ... } function Test-TargetResource { [OutputType([System.Boolean])] param ( [parameter(Mandatory = $true)] [String] $Name ) ... } ``` ## Example 2 ### Problematic code ```powershell theme={null} [DscResource()] class MyDSCResource { [DscProperty(Key)] [string] $Name [void] Set() { ... } [bool] Test() { ... } } ``` ### Correct code ```powershell theme={null} [DscResource()] class MyDSCResource { [DscProperty(Key)] [string] $Name [MyDSCResource] Get() { ... } [void] Set() { ... } [bool] Test() { ... } } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [DSCStandardDSCFunctionsInResource](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/DSCStandardDSCFunctionsInResource.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSDSCUseIdenticalMandatoryParametersForDSC Source: https://tally.wharflab.com/rules/powershell/PSDSCUseIdenticalMandatoryParametersForDSC Use identical mandatory parameters for DSC Get/Test/Set TargetResource functions `powershell/PSDSCUseIdenticalMandatoryParametersForDSC` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Error | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default because Desired State Configuration resources are out of scope for Dockerfile `RUN` analysis. Re-enable it with `include = ["powershell/PSDSCUseIdenticalMandatoryParametersForDSC"]` or by setting `rules.powershell.PSDSCUseIdenticalMandatoryParametersForDSC.severity = "warning"` in `.tally.toml`. ## Description For script-based DSC resources, if a property is declared with attributes `Key` or `Required` in a MOF file, then it should be present as a mandatory parameter in the corresponding `Get-TargetResource`, `Set-TargetResource` and `Test-TargetResource` functions. ## How Make sure all the properties with `Key` or `Required` attributes have equivalent mandatory parameters in the `Get/Set/Test` functions. ## Examples Consider the following `mof` file. ```powershell theme={null} class WaitForAny : OMI_BaseResource { [key, Description("Name of Resource on remote machine")] string Name; [required, Description("List of remote machines")] string NodeName[]; }; ``` ### Problematic code ```powershell theme={null} function Get-TargetResource { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Message ) } function Set-TargetResource { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Message, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Name ) } function Test-TargetResource { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Message, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Name ) } ``` ### Correct code ```powershell theme={null} function Get-TargetResource { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Message, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Name ) } function Set-TargetResource { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Message, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Name ) } function Test-TargetResource { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Message, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Name ) } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [DSCUseIdenticalMandatoryParametersForDSC](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/DSCUseIdenticalMandatoryParametersForDSC.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSDSCUseIdenticalParametersForDSC Source: https://tally.wharflab.com/rules/powershell/PSDSCUseIdenticalParametersForDSC Use Identical Parameters For DSC Test and Set Functions `powershell/PSDSCUseIdenticalParametersForDSC` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Error | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default because Desired State Configuration resources are out of scope for Dockerfile `RUN` analysis. Re-enable it with `include = ["powershell/PSDSCUseIdenticalParametersForDSC"]` or by setting `rules.powershell.PSDSCUseIdenticalParametersForDSC.severity = "warning"` in `.tally.toml`. ## Description The `Get-TargetResource`, `Test-TargetResource` and `Set-TargetResource` functions of DSC Resource must have the same parameters. ## How Correct the parameters for the functions in DSC resource. ## Examples ### Problematic code ```powershell theme={null} function Get-TargetResource { [OutputType([Hashtable])] param ( [parameter(Mandatory = $true)] [String] $Name, [String] $TargetResource ) ... } function Set-TargetResource { param ( [parameter(Mandatory = $true)] [String] $Name ) ... } function Test-TargetResource { [OutputType([System.Boolean])] param ( [parameter(Mandatory = $true)] [String] $Name ) ... } ``` ### Correct code ```powershell theme={null} function Get-TargetResource { [OutputType([Hashtable])] param ( [parameter(Mandatory = $true)] [String] $Name, [String] $TargetResource ) ... } function Set-TargetResource { param ( [parameter(Mandatory = $true)] [String] $Name, [String] $TargetResource ) ... } function Test-TargetResource { [OutputType([System.Boolean])] param ( [parameter(Mandatory = $true)] [String] $Name, [String] $TargetResource ) ... } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [DSCUseIdenticalParametersForDSC](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/DSCUseIdenticalParametersForDSC.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSDSCUseVerboseMessageInDSCResource Source: https://tally.wharflab.com/rules/powershell/PSDSCUseVerboseMessageInDSCResource Use verbose message in DSC resource `powershell/PSDSCUseVerboseMessageInDSCResource` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Information | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default because Desired State Configuration resources are out of scope for Dockerfile `RUN` analysis. Re-enable it with `include = ["powershell/PSDSCUseVerboseMessageInDSCResource"]` or by setting `rules.powershell.PSDSCUseVerboseMessageInDSCResource.severity = "warning"` in `.tally.toml`. ## Description Best practice recommends that additional user information is provided within commands, functions and scripts using `Write-Verbose`. ## How Make use of the `Write-Verbose` command. ## Examples ### Problematic code ```powershell theme={null} Function Test-Function { [CmdletBinding()] Param() ... } ``` ### Correct code ```powershell theme={null} Function Test-Function { [CmdletBinding()] Param() Write-Verbose 'Verbose output' ... } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [DSCUseVerboseMessageInDSCResource](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/DSCUseVerboseMessageInDSCResource.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSMisleadingBacktick Source: https://tally.wharflab.com/rules/powershell/PSMisleadingBacktick Misleading Backtick `powershell/PSMisleadingBacktick` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Checks that lines don't end with a backtick followed by whitespace. ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [MisleadingBacktick](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/MisleadingBacktick.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSMissingModuleManifestField Source: https://tally.wharflab.com/rules/powershell/PSMissingModuleManifestField Module Manifest Fields `powershell/PSMissingModuleManifestField` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default because Dockerfiles do not ship PowerShell module manifests (`.psd1` files) — this rule targets module-authoring artifacts that are out of scope for container builds. Re-enable it with `include = ["powershell/PSMissingModuleManifestField"]` or by setting `rules.powershell.PSMissingModuleManifestField.severity = "warning"` in `.tally.toml`. ## Description A module manifest is a `.psd1` file that contains a hash table. The keys and values in the hash table describe the contents and attributes of the module, define the prerequisites, and determine how the components are processed. Module manifests must contain the following keys (and a corresponding value) to be considered valid: * `ModuleVersion` All other keys are optional. The order of the entries is not important. ## How Please consider adding the missing fields to the manifest. ## Examples ### Problematic code ```powershell theme={null} @{ Author = 'PowerShell Author' NestedModules = @('.\mymodule.psm1') FunctionsToExport = '*' CmdletsToExport = '*' VariablesToExport = '*' } ``` ### Correct code ```powershell theme={null} @{ ModuleVersion = '1.0' Author = 'PowerShell Author' NestedModules = @('.\mymodule.psm1') FunctionsToExport = '*' CmdletsToExport = '*' VariablesToExport = '*' } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [MissingModuleManifestField](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/MissingModuleManifestField.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSPlaceCloseBrace Source: https://tally.wharflab.com/rules/powershell/PSPlaceCloseBrace Place close braces `powershell/PSPlaceCloseBrace` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Close brace placement should follow a consistent style. It should be on a new line by itself and should not be followed by an empty line. **Note**: This rule is not enabled by default. The user needs to enable it through settings. ## Configuration ```powershell theme={null} Rules = @{ PSPlaceCloseBrace = @{ Enable = $true NoEmptyLineBefore = $false IgnoreOneLineBlock = $true NewLineAfter = $true } } ``` ### Parameters #### Enable: bool (Default value is `$false`) Enable or disable the rule during ScriptAnalyzer invocation. #### NoEmptyLineBefore: bool (Default value is `$false`) Create violation if there is an empty line before a close brace. #### IgnoreOneLineBlock: bool (Default value is `$true`) Indicates if closed brace pairs in a one line block should be ignored or not. For example, `$x = if ($true) { 'blah' } else { 'blah blah' }`, if the property is set to true then the rule doesn't fire a violation. #### NewLineAfter: bool (Default value is `$true`) Indicates if a new line should follow a close brace. If set to true a close brace should be followed by a new line. ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [PlaceCloseBrace](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/PlaceCloseBrace.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSPlaceOpenBrace Source: https://tally.wharflab.com/rules/powershell/PSPlaceOpenBrace Place open braces consistently `powershell/PSPlaceOpenBrace` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Open brace placement should follow a consistent style. It can either follow K\&R style (on same line) or the Allman style (not on same line). **Note**: This rule is not enabled by default. The user needs to enable it through settings. ## Configuration ```powershell theme={null} Rules = @{ PSPlaceOpenBrace = @{ Enable = $true OnSameLine = $true NewLineAfter = $true IgnoreOneLineBlock = $true } } ``` ### Parameters #### Enable: bool (Default value is `$false`) Enable or disable the rule during ScriptAnalyzer invocation. #### OnSameLine: bool (Default value is `$true`) Enforce open brace to be on the same line as that of its preceding keyword. #### NewLineAfter: bool (Default value is `$true`) Enforce a new line character after an open brace. The default value is true. #### IgnoreOneLineBlock: bool (Default value is `$true`) Indicates if open braces in a one line block should be ignored or not. For example, `$x = if ($true) { 'blah' } else { 'blah blah' }`, if the property is set to true then the rule doesn't fire a violation. ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [PlaceOpenBrace](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/PlaceOpenBrace.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSPossibleIncorrectComparisonWithNull Source: https://tally.wharflab.com/rules/powershell/PSPossibleIncorrectComparisonWithNull Null Comparison `powershell/PSPossibleIncorrectComparisonWithNull` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description To ensure that PowerShell performs comparisons correctly, the `$null` element should be on the left side of the operator. There are multiple reasons why this occurs: * `$null` is a scalar value. When the value on the left side of an operator is a scalar, comparison operators return a **Boolean** value. When the value is a collection, the comparison operators return any matching values or an empty array if there are no matches in the collection. * PowerShell performs type casting on the right-hand operand, resulting in incorrect comparisons when `$null` is cast to other scalar types. The only way to reliably check if a value is `$null` is to place `$null` on the left side of the operator so that a scalar comparison is performed. ## How Move `$null` to the left side of the comparison. ## Examples ### Problematic code ```powershell theme={null} function Test-CompareWithNull { if ($DebugPreference -eq $null) { } } ``` ### Correct code ```powershell theme={null} function Test-CompareWithNull { if ($null -eq $DebugPreference) { } } ``` ## Try it Yourself ```powershell theme={null} # This example returns 'false' because the comparison does not return any objects from the array if (@() -eq $null) { 'true' } else { 'false' } # This example returns 'true' because the array is empty if ($null -ne @()) { 'true' } else { 'false' } ``` This is how the comparison operator works by-design. But, as demonstrated, this can lead to non-intuitive behavior, especially when the intent is a simple test for null. The following example demonstrates the designed behavior of the comparison operator when the left-hand side is a collection. Each element in the collection is compared to the right-hand side value. When true, that element of the collection is returned. ```powershell theme={null} PS> 1,2,3,1,2 -eq $null PS> 1,2,3,1,2 -eq 1 1 1 PS> (1,2,3,1,2 -eq $null).count 0 PS> (1,2,$null,3,$null,1,2 -eq $null).count 2 ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [PossibleIncorrectComparisonWithNull](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/PossibleIncorrectComparisonWithNull.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSPossibleIncorrectUsageOfAssignmentOperator Source: https://tally.wharflab.com/rules/powershell/PSPossibleIncorrectUsageOfAssignmentOperator Equal sign is not an assignment operator. Did you mean the equality operator \'-eq\'? `powershell/PSPossibleIncorrectUsageOfAssignmentOperator` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Information | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description In many programming languages, the equality operator is denoted as `==` or `=`, but `PowerShell` uses `-eq`. Therefore, it can easily happen that the wrong operator is used unintentionally. This rule catches a few special cases where the likelihood of that is quite high. The rule looks for usages of `==` and `=` operators inside `if`, `else if`, `while` and `do-while` statements but it does not warn if any kind of command or expression is used at the right hand side as this is probably by design. ## Examples ### Problematic code ```powershell theme={null} if ($a = $b) { ... } ``` ```powershell theme={null} if ($a == $b) { } ``` ### Correct code ```powershell theme={null} if ($a -eq $b) # Compare $a with $b { ... } ``` ```powershell theme={null} if ($a = Get-Something) # Only execute action if command returns something and assign result to variable { Do-SomethingWith $a } ``` ## Implicit suppression using Clang style There are some rare cases where assignment of variable inside an `if` statement is by design. Instead of suppressing the rule, one can also signal that assignment was intentional by wrapping the expression in extra parenthesis. An exception for this is when `$null` is used on the LHS because there is no use case for this. ```powershell theme={null} if (($shortVariableName = $SuperLongVariableName['SpecialItem']['AnotherItem'])) { ... } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [PossibleIncorrectUsageOfAssignmentOperator](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/PossibleIncorrectUsageOfAssignmentOperator.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSPossibleIncorrectUsageOfRedirectionOperator Source: https://tally.wharflab.com/rules/powershell/PSPossibleIncorrectUsageOfRedirectionOperator \'>\' is not a comparison operator. Use \'-gt\' (greater than) or \'-ge\' (greater or equal). `powershell/PSPossibleIncorrectUsageOfRedirectionOperator` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Information | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description In many programming languages, the comparison operator for 'greater than' is `>` but `PowerShell` uses `-gt` for it and `-ge` (greater or equal) for `>=`. Therefore, it can easily happen that the wrong operator is used unintentionally. This rule catches a few special cases where the likelihood of that is quite high. The rule looks for usages of `>` or `>=` operators inside `if`, `elseif`, `while` and `do-while` statements because this is likely going to be unintentional usage. ## Examples ### Problematic code ```powershell theme={null} if ($a > $b) { ... } ``` ### Correct code ```powershell theme={null} if ($a -gt $b) { ... } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [PossibleIncorrectUsageOfRedirectionOperator](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/PossibleIncorrectUsageOfRedirectionOperator.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSProvideCommentHelp Source: https://tally.wharflab.com/rules/powershell/PSProvideCommentHelp Basic Comment Help `powershell/PSProvideCommentHelp` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Info | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSProvideCommentHelp"]` or by setting `rules.powershell.PSProvideCommentHelp.severity = "warning"` in `.tally.toml`. ## Description Comment based help should be provided for all PowerShell commands. This test only checks for the presence of comment based help and not on the validity or format. For assistance on comment based help, use the command `Get-Help about_comment_based_help` or the following articles: * [Writing Comment-based Help][01] * [Writing Help for PowerShell Cmdlets][02] * [Create XML-based help using PlatyPS][03] ## Configuration ```powershell theme={null} Rules = @{ PSProvideCommentHelp = @{ Enable = $true ExportedOnly = $false BlockComment = $true VSCodeSnippetCorrection = $false Placement = 'before' } } ``` ### Parameters * `Enable`: **bool** (Default value is `$true`) Enable or disable the rule during ScriptAnalyzer invocation. * `ExportedOnly`: **bool** (Default value is `$true`) If enabled, throw violation only on functions/cmdlets that are exported using the `Export-ModuleMember` cmdlet. * `BlockComment`: **bool** (Default value is `$true`) If enabled, returns comment help in block comment style (`<#...#>`). Otherwise returns comment help in line comment style where each comment line starts with `#`. * `VSCodeSnippetCorrection`: **bool** (Default value is `$false`) If enabled, returns comment help in vscode snippet format. * `Placement`: **string** (Default value is `before`) Represents the position of comment help with respect to the function definition. Possible values are: * `before`: means the help is placed before the function definition * `begin` means the help is placed at the beginning of the function definition body * `end` means the help is places the end of the function definition body If any invalid value is given, the property defaults to `before`. ## Examples ### Problematic code ```powershell theme={null} function Get-File { [CmdletBinding()] Param ( ... ) } ``` ### Correct code ```powershell theme={null} <# .Synopsis Short description .DESCRIPTION Long description .EXAMPLE Example of how to use this cmdlet .EXAMPLE Another example of how to use this cmdlet .INPUTS Inputs to this cmdlet (if any) .OUTPUTS Output from this cmdlet (if any) .NOTES General notes .COMPONENT The component this cmdlet belongs to .ROLE The role this cmdlet belongs to .FUNCTIONALITY The functionality that best describes this cmdlet #> function Get-File { [CmdletBinding()] Param ( ... ) } ``` [01]: /powershell/scripting/developer/help/writing-comment-based-help-topics [02]: /powershell/scripting/developer/help/writing-help-for-windows-powershell-cmdlets [03]: /powershell/utility-modules/platyps/create-help-using-platyps ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [ProvideCommentHelp](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/ProvideCommentHelp.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSReservedCmdletChar Source: https://tally.wharflab.com/rules/powershell/PSReservedCmdletChar Reserved Cmdlet Chars `powershell/PSReservedCmdletChar` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Error | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSReservedCmdletChar"]` or by setting `rules.powershell.PSReservedCmdletChar.severity = "warning"` in `.tally.toml`. ## Description You cannot use following reserved characters in a function or cmdlet name as these can cause parsing or runtime errors. Reserved Characters include: ``#,(){}[]&/\\$^;:\"'<>|?@`*%+=~`` ## How Remove reserved characters from names. ## Examples ### Problematic code ```powershell theme={null} function MyFunction[1] {...} ``` ### Correct code ```powershell theme={null} function MyFunction {...} ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [ReservedCmdletChar](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/ReservedCmdletChar.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSReservedParams Source: https://tally.wharflab.com/rules/powershell/PSReservedParams Reserved Parameters `powershell/PSReservedParams` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Error | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSReservedParams"]` or by setting `rules.powershell.PSReservedParams.severity = "warning"` in `.tally.toml`. ## Description You can't redefine [common parameters][01] in an advanced function. Using the `CmdletBinding` or `Parameter` attributes creates an advanced function. The common parameters are are automatically available in advanced functions, so you can't redefine them. ## How Change the name of the parameter. ## Examples ### Problematic code ```powershell theme={null} function Test { [CmdletBinding()] Param ( $ErrorVariable, $Parameter2 ) } ``` ### Correct code ```powershell theme={null} function Test { [CmdletBinding()] Param ( $Err, $Parameter2 ) } ``` [01]: /powershell/module/microsoft.powershell.core/about/about_commonparameters ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [ReservedParams](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/ReservedParams.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSReviewUnusedParameter Source: https://tally.wharflab.com/rules/powershell/PSReviewUnusedParameter ReviewUnusedParameter `powershell/PSReviewUnusedParameter` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSReviewUnusedParameter"]` or by setting `rules.powershell.PSReviewUnusedParameter.severity = "warning"` in `.tally.toml`. ## Description This rule identifies parameters declared in a script, scriptblock, or function scope that have not been used in that scope. ## Configuration settings By default, this rule doesn't consider child scopes other than scriptblocks provided to `Where-Object` or `ForEach-Object`. The `CommandsToTraverse` setting is a string array that allows you to add additional commands that accept scriptblocks that this rule should examine. ```powershell theme={null} @{ Rules = @{ PSReviewUnusedParameter = @{ CommandsToTraverse = @( 'Invoke-PSFProtectedCommand' ) } } } ``` ## How Consider removing the unused parameter. ## Examples ### Problematic code ```powershell theme={null} function Test-Parameter { Param ( $Parameter1, # this parameter is never called in the function $Parameter2 ) Get-Something $Parameter1 } ``` ### Correct code ```powershell theme={null} function Test-Parameter { Param ( $Parameter1, # now this parameter is being called in the same scope $Parameter2 ) Get-Something $Parameter1 $Parameter2 } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [ReviewUnusedParameter](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/ReviewUnusedParameter.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSShouldProcess Source: https://tally.wharflab.com/rules/powershell/PSShouldProcess Should Process `powershell/PSShouldProcess` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSShouldProcess"]` or by setting `rules.powershell.PSShouldProcess.severity = "warning"` in `.tally.toml`. ## Description If a cmdlet declares the `SupportsShouldProcess` attribute, then it should also call `ShouldProcess`. A violation is any function which either declares `SupportsShouldProcess` attribute but makes no calls to `ShouldProcess` or it calls `ShouldProcess` but does not declare `SupportsShouldProcess`. For more information, see the following articles: * [about\_Functions\_Advanced\_Methods][01] * [about\_Functions\_CmdletBindingAttribute][02] * [Everything you wanted to know about ShouldProcess][03] ## How To fix a violation of this rule, please call `ShouldProcess` method when a cmdlet declares `SupportsShouldProcess` attribute. Or please add `SupportsShouldProcess` attribute argument when calling `ShouldProcess`. ## Examples ### Problematic code ```powershell theme={null} function Set-File { [CmdletBinding(SupportsShouldProcess=$true)] Param ( # Path to file [Parameter(Mandatory=$true)] $Path ) 'String' | Out-File -FilePath $Path } ``` ### Correct code ```powershell theme={null} function Set-File { [CmdletBinding(SupportsShouldProcess=$true)] Param ( # Path to file [Parameter(Mandatory=$true)] $Path, [Parameter(Mandatory=$true)] [string]$Content ) if ($PSCmdlet.ShouldProcess($Path, ("Setting content to '{0}'" -f $Content))) { $Content | Out-File -FilePath $Path } else { # Code that should be processed if doing a WhatIf operation # Must NOT change anything outside of the function / script } } ``` [01]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_functions_advanced_methods [02]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_functions_cmdletbindingattribute [03]: https://learn.microsoft.com/powershell/scripting/learn/deep-dives/everything-about-shouldprocess ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [ShouldProcess](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/ShouldProcess.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseApprovedVerbs Source: https://tally.wharflab.com/rules/powershell/PSUseApprovedVerbs Cmdlet Verbs `powershell/PSUseApprovedVerbs` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSUseApprovedVerbs"]` or by setting `rules.powershell.PSUseApprovedVerbs.severity = "warning"` in `.tally.toml`. ## Description All cmdlets must use approved verbs. Approved verbs can be found by running the command `Get-Verb`. For more information about approved verbs, see [Approved Verbs for PowerShell Commands][01]. Some unapproved verbs are documented on the approved verbs page and point to approved alternatives. Try searching for the verb you used to find its approved form. For example, searching for `Read`, `Open`, or `Search` leads you to `Get`. ## How Change the verb in the cmdlet's name to an approved verb. ## Examples ### Problematic code ```powershell theme={null} function Change-Item { ... } ``` ### Correct code ```powershell theme={null} function Update-Item { ... } ``` [01]: https://learn.microsoft.com/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseApprovedVerbs](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseApprovedVerbs.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseBOMForUnicodeEncodedFile Source: https://tally.wharflab.com/rules/powershell/PSUseBOMForUnicodeEncodedFile Use BOM encoding for non-ASCII files `powershell/PSUseBOMForUnicodeEncodedFile` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default because Dockerfile `RUN` bodies are inline strings, not files on disk with encoding metadata. Re-enable it with `include = ["powershell/PSUseBOMForUnicodeEncodedFile"]` or by setting `rules.powershell.PSUseBOMForUnicodeEncodedFile.severity = "warning"` in `.tally.toml`. ## Description For a file encoded with a format other than ASCII, ensure Byte Order Mark (BOM) is present to ensure that any application consuming this file can interpret it correctly. You can use this rule to test any arbitrary text file, but the intent is to ensure that PowerShell scripts are saved with a BOM when using a Unicode encoding. ## How For PowerShell commands that write to files, ensure that you set the encoding parameter to a value that produces a BOM. In PowerShell 7 and higher, the following values of the **Encoding** parameter produce a BOM: * `bigendianunicode` * `bigendianutf32` * `oem` * `unicode` * `utf32` * `utf8BOM` When you create a script file using a text editor, ensure that the editor is configured to save the file with a BOM. Consult the documentation for your text editor for instructions on how to save files with a BOM. ## Further reading For more information, see the following articles: * [about\_Character\_Encoding](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_character_encoding) * [Set-Content](https://learn.microsoft.com/powershell/module/microsoft.powershell.management/set-content) * [Understanding file encoding in VS Code and PowerShell](https://learn.microsoft.com/powershell/scripting/dev-cross-plat/vscode/understanding-file-encoding) ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseBOMForUnicodeEncodedFile](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseBOMForUnicodeEncodedFile.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseCmdletCorrectly Source: https://tally.wharflab.com/rules/powershell/PSUseCmdletCorrectly Use Cmdlet Correctly `powershell/PSUseCmdletCorrectly` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Whenever we call a command, care should be taken that it is invoked with the correct syntax and parameters. ## How Specify all mandatory parameters when calling commands. ## Examples ### Problematic code ```powershell theme={null} Function Set-TodaysDate () { Set-Date ... } ``` ### Correct code ```powershell theme={null} Function Set-TodaysDate () { $date = Get-Date Set-Date -Date $date ... } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseCmdletCorrectly](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseCmdletCorrectly.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseCompatibleCmdlets Source: https://tally.wharflab.com/rules/powershell/PSUseCompatibleCmdlets Use compatible cmdlets `powershell/PSUseCompatibleCmdlets` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description This rule flags cmdlets that aren't available in a given Edition and Version of PowerShell on a given Operating System. It works by comparing a cmdlet against a set of allowlists which ship with PSScriptAnalyzer. They can be found at `/path/to/PSScriptAnalyzerModule/Settings`. These files use the `--.json` form, where `` can be either `Core` or `Desktop`, `` can be either `Windows`, `Linux` or `macOS`, and `` is the PowerShell version. To enable the rule to check if your script is compatible on PowerShell Core on Windows, put the following in your settings file: tally forwards matching `rules.powershell.PSUseCompatibleCmdlets` options, such as `compatibility`, to PSScriptAnalyzer. ```powershell theme={null} @{ 'Rules' = @{ 'PSUseCompatibleCmdlets' = @{ 'compatibility' = @('core-6.1.0-windows') } } } ``` The parameter `compatibility` is a list that contains any of the following: * desktop-2.0-windows * desktop-3.0-windows * desktop-4.0-windows (taken from Windows Server 2012R2) * desktop-5.1.14393.206-windows * core-6.1.0-windows (taken from Windows 10 - 1803) * core-6.1.0-linux (taken from Ubuntu 18.04) * core-6.1.0-linux-arm (taken from Raspbian) * core-6.1.0-macos The `desktop-*` values are upstream PSScriptAnalyzer compatibility targets, not tally analyzer host support. tally runs the analyzer through PowerShell 7 (`pwsh`); Windows PowerShell 5.1 (`powershell.exe`) is out of scope as a sidecar host. Usually, patched versions of PowerShell have the same cmdlet data, therefore only settings of major and minor versions of PowerShell are supplied. You can also create a custom settings file with the [New-CommandDataFile.ps1][01] script. Place the created `.json` file in the `Settings` folder of the `PSScriptAnalyzer` module folder. Then the `compatibility` parameter value is just the filename. Note that the `core-6.0.2-*` files were removed in PSScriptAnalyzer 1.18 since PowerShell 6.0 reached its end of life. [01]: https://github.com/PowerShell/PSScriptAnalyzer/blob/main/Utils/New-CommandDataFile.ps1 ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseCompatibleCmdlets](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseCompatibleCmdlets.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseCompatibleCommands Source: https://tally.wharflab.com/rules/powershell/PSUseCompatibleCommands Use compatible commands `powershell/PSUseCompatibleCommands` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description This rule identifies commands that are not available on a targeted PowerShell platform. This page describes upstream PSScriptAnalyzer compatibility profiles. tally runs the analyzer through PowerShell 7 (`pwsh`); Windows PowerShell 5.1 (`powershell.exe`) is out of scope as a sidecar host. A PowerShell platform is identified by a name in the following format: ```text theme={null} ``_``_``_``_``_``_`` ``` Where: * ``: The name of the operating system PowerShell is running on. On Windows, this includes the SKU number. On Linux, this is the name of the distribution. * ``: The machine architecture the operating system is running on (this is usually `x64`). * ``: The self-reported version of the operating system (on Linux, this is the distribution version). * ``: The PowerShell version (from `$PSVersionTable.PSVersion`). * ``: The machine architecture of the PowerShell process. * ``: The reported version of the .NET runtime PowerShell is running on (from `System.Environment.Version`). * ``: The .NET runtime flavor PowerShell is running on (currently `framework` or `core`). For example: * `win-4_x64_10.0.18312.0_5.1.18312.1000_x64_4.0.30319.42000_framework` is PowerShell 5.1 running on Windows 10 Enterprise (build 18312) for x64. * `win-4_x64_10.0.18312.0_6.1.2_x64_4.0.30319.42000_core` is PowerShell 6.1.2 running on the same operating system. * `ubuntu_x64_18.04_6.2.0_x64_4.0.30319.42000_core` is PowerShell 6.2.0 running on Ubuntu 18.04. Some platforms come bundled with PSScriptAnalyzer as JSON files, named in this way for targeting in your configuration. Platforms bundled by default are: | PowerShell Version | Operating System | ID | | :----------------: | ---------------------- | --------------------------------------------------------------------- | | 3.0 | Windows Server 2012 | `win-8_x64_6.2.9200.0_3.0_x64_4.0.30319.42000_framework` | | 4.0 | Windows Server 2012 R2 | `win-8_x64_6.3.9600.0_4.0_x64_4.0.30319.42000_framework` | | 5.1 | Windows Server 2016 | `win-8_x64_10.0.14393.0_5.1.14393.2791_x64_4.0.30319.42000_framework` | | 5.1 | Windows Server 2019 | `win-8_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework` | | 5.1 | Windows 10 Pro | `win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework` | | 6.2 | Ubuntu 18.04 LTS | `ubuntu_x64_18.04_6.2.4_x64_4.0.30319.42000_core` | | 6.2 | Windows 10.0.14393 | `win-8_x64_10.0.14393.0_6.2.4_x64_4.0.30319.42000_core` | | 6.2 | Windows 10.0.17763 | `win-8_x64_10.0.17763.0_6.2.4_x64_4.0.30319.42000_core` | | 6.2 | Windows 10.0.18362 | `win-4_x64_10.0.18362.0_6.2.4_x64_4.0.30319.42000_core` | | 7.0 | Ubuntu 18.04 LTS | `ubuntu_x64_18.04_7.0.0_x64_3.1.2_core` | | 7.0 | Windows 10.0.14393 | `win-8_x64_10.0.14393.0_7.0.0_x64_3.1.2_core` | | 7.0 | Windows 10.0.17763 | `win-8_x64_10.0.17763.0_7.0.0_x64_3.1.2_core` | | 7.0 | Windows 10.0.18362 | `win-4_x64_10.0.18362.0_7.0.0_x64_3.1.2_core` | Other profiles can be found in the [GitHub repo][02]. You can also generate your own platform profile using the [PSCompatibilityCollector module][01]. The compatibility profile settings takes a list of platforms to target under `TargetProfiles`. A platform can be specified as: * A platform name (like `ubuntu_x64_18.04_6.1.1_x64_4.0.30319.42000_core`), which will have `.json` added to the end and is searched for in the default profile directory. * A filename (like `my_custom_platform.json`), which will be searched for in the default profile directory. * An absolute path to a file (like `D:\PowerShellProfiles\TargetMachine.json`). The default profile directory is under the PSScriptAnalyzer module at `$PSScriptRoot/compatibility_profiles` (where `$PSScriptRoot` here refers to the directory containing `PSScriptAnalyzer.psd1`). The compatibility analysis compares a command used to both a target profile and a 'union' profile (containing all commands available in *any* profile in the profile dir). If a command is not present in the union profile, it is assumed to be locally created and ignored. Otherwise, if a command is present in the union profile but not present in a target, it is deemed to be incompatible with that target. ## Configuration settings | Configuration key | Meaning | Accepted values | Mandatory | Example | | ----------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | | `Enable` | Activates the rule | bool (`$true`/`$false`) | No (default: `$false`) | `$true` | | `TargetProfiles` | The list of PowerShell profiles to target | string\[]: absolute paths to profile files or names of profiles in the profile directory | No (default: `@()`) | `@('ubuntu_x64_18.04_6.1.3_x64_4.0.30319.42000_core', 'win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework')` | | `ProfileDirPath` | The location to search for profiles by name and use for union profile generation | string: absolute path to new profile dir | No (defaults to `compatibility_profiles` directory in PSScriptAnalyzer module) | `C:\Users\me\Documents\pssaCompatProfiles` | | `IgnoreCommands` | Commands to ignore compatibility of in scripts | string\[]: names of commands to ignore | No (default: `@()`) | `@('Get-ChildItem','Import-Module')` | An example configuration might look like: ```powershell theme={null} @{ Rules = @{ PSUseCompatibleCommands = @{ Enable = $true TargetProfiles = @( 'ubuntu_x64_18.04_6.1.3_x64_4.0.30319.42000_core' 'win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework' 'MyProfile' 'another_custom_profile_in_the_profiles_directory.json' 'D:\My Profiles\profile1.json' ) # You can specify commands to not check like this, which also will ignore its parameters: IgnoreCommands = @( 'Install-Module' ) } } } ``` ## Suppression Command compatibility diagnostics can be suppressed with an attribute on the `param` block of a scriptblock as with other rules. ```powershell theme={null} [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseCompatibleCommands', '')] ``` The rule can also be suppressed only for particular commands: ```powershell theme={null} [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseCompatibleCommands', 'Start-Service')] ``` And also suppressed only for parameters: ```powershell theme={null} [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseCompatibleCommands', 'Import-Module/FullyQualifiedName')] ``` [01]: https://github.com/PowerShell/PSScriptAnalyzer/tree/main/PSCompatibilityCollector [02]: https://github.com/PowerShell/PSScriptAnalyzer/tree/main/PSCompatibilityCollector/optional_profiles ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseCompatibleCommands](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseCompatibleCommands.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseCompatibleSyntax Source: https://tally.wharflab.com/rules/powershell/PSUseCompatibleSyntax Use compatible syntax `powershell/PSUseCompatibleSyntax` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description This rule identifies syntax elements that are incompatible with targeted PowerShell versions. It cannot identify syntax elements incompatible with PowerShell 3 or 4 when run from those PowerShell versions because they aren't able to parse the incompatible syntaxes. ```powershell theme={null} @{ Rules = @{ PSUseCompatibleSyntax = @{ Enable = $true TargetVersions = @( '6.0', '5.1', '4.0' ) } } } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseCompatibleSyntax](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseCompatibleSyntax.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseCompatibleTypes Source: https://tally.wharflab.com/rules/powershell/PSUseCompatibleTypes Use compatible types `powershell/PSUseCompatibleTypes` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description This rule identifies types that are not available (loaded by default) in targeted PowerShell platforms. A PowerShell platform is identified by a name in the following format: ```text theme={null} ``_``_``_``_``_``_`` ``` Where: * ``: The name of the operating system PowerShell is running on. On Windows, this includes the SKU number. On Linux, this is the name of the distribution. * ``: The machine architecture the operating system is running on (this is usually `x64`). * ``: The self-reported version of the operating system (on Linux, this is the distribution version). * ``: The PowerShell version (from `$PSVersionTable.PSVersion`). * ``: The machine architecture of the PowerShell process. * ``: The reported version of the .NET runtime PowerShell is running on (from `System.Environment.Version`). * ``: The .NET runtime flavor PowerShell is running on (currently `framework` or `core`). For example: * `win-4_x64_10.0.18312.0_5.1.18312.1000_x64_4.0.30319.42000_framework` is PowerShell 5.1 running on Windows 10 Enterprise (build 18312) for x64. * `win-4_x64_10.0.18312.0_6.1.2_x64_4.0.30319.42000_core` is PowerShell 6.1.2 running on the same operating system. * `ubuntu_x64_18.04_6.2.0_x64_4.0.30319.42000_core` is PowerShell 6.2.0 running on Ubuntu 18.04. Some platforms come bundled with PSScriptAnalyzer as JSON files, named in this way for targeting in your configuration. Platforms bundled by default are: | PowerShell Version | Operating System | ID | | :----------------: | ---------------------- | --------------------------------------------------------------------- | | 3.0 | Windows Server 2012 | `win-8_x64_6.2.9200.0_3.0_x64_4.0.30319.42000_framework` | | 4.0 | Windows Server 2012 R2 | `win-8_x64_6.3.9600.0_4.0_x64_4.0.30319.42000_framework` | | 5.1 | Windows Server 2016 | `win-8_x64_10.0.14393.0_5.1.14393.2791_x64_4.0.30319.42000_framework` | | 5.1 | Windows Server 2019 | `win-8_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework` | | 5.1 | Windows 10 Pro | `win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework` | | 6.2 | Ubuntu 18.04 LTS | `ubuntu_x64_18.04_6.2.4_x64_4.0.30319.42000_core` | | 6.2 | Windows 10.0.14393 | `win-8_x64_10.0.14393.0_6.2.4_x64_4.0.30319.42000_core` | | 6.2 | Windows 10.0.17763 | `win-8_x64_10.0.17763.0_6.2.4_x64_4.0.30319.42000_core` | | 6.2 | Windows 10.0.18362 | `win-4_x64_10.0.18362.0_6.2.4_x64_4.0.30319.42000_core` | | 7.0 | Ubuntu 18.04 LTS | `ubuntu_x64_18.04_7.0.0_x64_3.1.2_core` | | 7.0 | Windows 10.0.14393 | `win-8_x64_10.0.14393.0_7.0.0_x64_3.1.2_core` | | 7.0 | Windows 10.0.17763 | `win-8_x64_10.0.17763.0_7.0.0_x64_3.1.2_core` | | 7.0 | Windows 10.0.18362 | `win-4_x64_10.0.18362.0_7.0.0_x64_3.1.2_core` | Other profiles can be found in the [GitHub repo][02]. You can also generate your own platform profile using the [PSCompatibilityCollector module][01]. The compatibility profile settings takes a list of platforms to target under `TargetProfiles`. A platform can be specified as: * A platform name (like `ubuntu_x64_18.04_6.1.1_x64_4.0.30319.42000_core`), which will have `.json` added to the end and is searched for in the default profile directory. * A filename (like `my_custom_platform.json`), which will be searched for in the default profile directory. * An absolute path to a file (like `D:\PowerShellProfiles\TargetMachine.json`). The default profile directory is under the PSScriptAnalyzer module at `$PSScriptRoot/PSCompatibilityCollector/profiles` (where `$PSScriptRoot` here refers to the directory containing `PSScriptAnalyzer.psd1`). The compatibility analysis compares a type used to both a target profile and a 'union' profile (containing all types available in *any* profile in the profile dir). If a type is not present in the union profile, it is assumed to be locally created and ignored. Otherwise, if a type is present in the union profile but not present in a target, it is deemed to be incompatible with that target. ## Configuration settings | Configuration key | Meaning | Accepted values | Mandatory | Example | | ----------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | | `Enable` | Activates the rule | bool (`$true`/`$false`) | No (default: `$false`) | `$true` | | `TargetProfiles` | The list of PowerShell profiles to target | string\[]: absolute paths to profile files or names of profiles in the profile directory | No (default: `@()`) | `@('ubuntu_x64_18.04_6.1.3_x64_4.0.30319.42000_core', 'win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework')` | | `ProfileDirPath` | The location to search for profiles by name and use for union profile generation | string: absolute path to new profile dir | No (defaults to `compatibility_profiles` directory in PSScriptAnalyzer module) | `C:\Users\me\Documents\pssaCompatProfiles` | | `IgnoreTypes` | Full names of types or type accelerators to ignore compatibility of in scripts | string\[]: names of types to ignore | No (default: `@()`) | `@('System.Collections.ArrayList','string')` | An example configuration might look like: ```powershell theme={null} @{ Rules = @{ PSUseCompatibleTypes = @{ Enable = $true TargetProfiles = @( 'ubuntu_x64_18.04_6.1.3_x64_4.0.30319.42000_core' 'win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework' 'MyProfile' 'another_custom_profile_in_the_profiles_directory.json' 'D:\My Profiles\profile1.json' ) # You can specify types to not check like this, which will also ignore methods and members on it: IgnoreTypes = @( 'System.IO.Compression.ZipFile' ) } } } ``` Alternatively, you could provide a settings object as follows: ```powershell theme={null} PS> $settings = @{ Rules = @{ PSUseCompatibleTypes = @{ Enable = $true TargetProfiles = @('win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework') } } } PS> Invoke-ScriptAnalyzer -Settings $settings -ScriptDefinition "[System.Management.Automation.SemanticVersion]'1.18.0-rc1'" RuleName Severity ScriptName Line Message -------- -------- ---------- ---- ------- PSUseCompatibleTypes Warning 1 The type 'System.Management.Automation.SemanticVersion' is not available by default in PowerShell version '5.1.17763.316' on platform 'Microsoft Windows 10 Pro' ``` ## Suppression Command compatibility diagnostics can be suppressed with an attribute on the `param` block of a scriptblock as with other rules. ```powershell theme={null} [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseCompatibleTypes', '')] ``` The rule can also be suppressed only for particular types: ```powershell theme={null} [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseCompatibleTypes', 'System.Management.Automation.Security.SystemPolicy')] ``` And also suppressed only for type members: ```powershell theme={null} [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseCompatibleTypes', 'System.Management.Automation.LanguagePrimitives/ConvertTypeNameToPSTypeName')] ``` [01]: https://github.com/PowerShell/PSScriptAnalyzer/tree/main/PSCompatibilityCollector [02]: https://github.com/PowerShell/PSScriptAnalyzer/tree/main/PSCompatibilityCollector/optional_profiles ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseCompatibleTypes](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseCompatibleTypes.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseConsistentIndentation Source: https://tally.wharflab.com/rules/powershell/PSUseConsistentIndentation Use consistent indentation `powershell/PSUseConsistentIndentation` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Indentation should be consistent throughout the source file. **Note**: This rule is not enabled by default. The user needs to enable it through settings. ## Configuration ```powershell theme={null} Rules = @{ PSUseConsistentIndentation = @{ Enable = $true IndentationSize = 4 PipelineIndentation = 'IncreaseIndentationForFirstPipeline' Kind = 'space' } } ``` ### Parameters #### Enable: bool (Default value is `$false`) Enable or disable the rule during ScriptAnalyzer invocation. #### IndentationSize: int (Default value is `4`) Indentation size in the number of space characters. #### PipelineIndentation: string (Default value is `IncreaseIndentationForFirstPipeline`) Whether to increase indentation after a pipeline for multi-line statements. The settings are: * IncreaseIndentationForFirstPipeline (default): Indent once after the first pipeline and keep this indentation. Example: ```powershell theme={null} foo | bar | baz ``` * IncreaseIndentationAfterEveryPipeline: Indent more after the first pipeline and keep this indentation. Example: ```powershell theme={null} foo | bar | baz ``` * NoIndentation: Do not increase indentation. Example: ```powershell theme={null} foo | bar | baz ``` * None: Do not change any existing pipeline indentation. #### Kind: string (Default value is `space`) Represents the kind of indentation to be used. Possible values are: `space`, `tab`. If any invalid value is given, the property defaults to `space`. `space` means `IndentationSize` number of `space` characters are used to provide one level of indentation. `tab` means a tab character, `\t`. ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseConsistentIndentation](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseConsistentIndentation.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseConsistentParameterSetName Source: https://tally.wharflab.com/rules/powershell/PSUseConsistentParameterSetName Use consistent parameter set names and proper parameter set configuration. `powershell/PSUseConsistentParameterSetName` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Parameter set names in PowerShell are case-sensitive, unlike most other PowerShell elements. This rule ensures consistent casing and proper configuration of parameter sets to avoid runtime errors and improve code clarity. The rule performs five different checks: 1. **Missing DefaultParameterSetName** - Warns when parameter sets are used but no default is specified 2. **Multiple parameter declarations** - Detects when a parameter is declared multiple times in the same parameter set. This is ultimately a runtime exception - this check helps catch it sooner. 3. **Case mismatch between DefaultParameterSetName and ParameterSetName** - Ensures consistent casing 4. **Case mismatch between different ParameterSetName values** - Ensures all references to the same parameter set use identical casing 5. **Parameter set names containing newlines** - Warns against using newline characters in parameter set names This rule isn't enabled by default. The user needs to enable it through settings. ## How * Use a `DefaultParameterSetName` when defining multiple parameter sets * Ensure consistent casing between `DefaultParameterSetName` and `ParameterSetName` values * Use identical casing for all references to the same parameter set name * Avoid declaring the same parameter multiple times in a single parameter set * Do not use newline characters in parameter set names ## Examples ### Problematic code ```powershell theme={null} # Missing DefaultParameterSetName function Get-Data { [CmdletBinding()] param( [Parameter(ParameterSetName='ByName')] [string]$Name, [Parameter(ParameterSetName='ByID')] [int]$ID ) } # Case mismatch between DefaultParameterSetName and ParameterSetName function Get-Data { [CmdletBinding(DefaultParameterSetName='ByName')] param( [Parameter(ParameterSetName='byname')] [string]$Name, [Parameter(ParameterSetName='ByID')] [int]$ID ) } # Inconsistent casing between ParameterSetName values function Get-Data { [CmdletBinding(DefaultParameterSetName='ByName')] param( [Parameter(ParameterSetName='ByName')] [string]$Name, [Parameter(ParameterSetName='byname')] [string]$DisplayName ) } # Multiple parameter declarations in same set function Get-Data { param( [Parameter(ParameterSetName='ByName')] [Parameter(ParameterSetName='ByName')] [string]$Name ) } # Parameter set name with newline function Get-Data { param( [Parameter(ParameterSetName="Set`nOne")] [string]$Name ) } ``` ### Correct code ```powershell theme={null} # Proper parameter set configuration function Get-Data { [CmdletBinding(DefaultParameterSetName='ByName')] param( [Parameter(ParameterSetName='ByName', Mandatory)] [string]$Name, [Parameter(ParameterSetName='ByName')] [Parameter(ParameterSetName='ByID')] [string]$ComputerName, [Parameter(ParameterSetName='ByID', Mandatory)] [int]$ID ) } ``` ## Configuration ```powershell theme={null} Rules = @{ PSUseConsistentParameterSetName = @{ Enable = $true } } ``` ### Parameters * `Enable`: **bool** (Default value is `$false`) Enable or disable the rule during ScriptAnalyzer invocation. ## Notes * Parameter set names are case-sensitive in PowerShell, making this different from most other PowerShell elements * The first occurrence of a parameter set name in your code is treated as the canonical casing * Parameters without `[Parameter()]` attributes are automatically part of all parameter sets * It's a PowerShell best practice to always specify a `DefaultParameterSetName` when using parameter sets ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseConsistentParameterSetName](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseConsistentParameterSetName.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseConsistentParametersKind Source: https://tally.wharflab.com/rules/powershell/PSUseConsistentParametersKind Use the same pattern when defining parameters. `powershell/PSUseConsistentParametersKind` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description All functions should use the same pattern when defining parameters. Possible pattern types are: 1. `Inline` ```powershell theme={null} function f([Parameter()]$FirstParam) { return } ``` 2. `ParamBlock` ```powershell theme={null} function f { param([Parameter()]$FirstParam) return } ``` In simple scenarios, both function definitions shown are considered to be equal. The purpose of this rule is to enforce consistent code style across the codebase. ## How to Fix Rewrite function so it defines parameters as specified in the rule ## Examples When the rule sets parameters definition kind to `Inline`: ```powershell theme={null} # Correct function f([Parameter()]$FirstParam) { return } # Incorrect function g { param([Parameter()]$FirstParam) return } ``` When the rule sets parameters definition kind to `ParamBlock`: ```powershell theme={null} # Incorrect function f([Parameter()]$FirstParam) { return } # Correct function g { param([Parameter()]$FirstParam) return } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseConsistentParametersKind](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseConsistentParametersKind.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseConsistentWhitespace Source: https://tally.wharflab.com/rules/powershell/PSUseConsistentWhitespace Use whitespaces `powershell/PSUseConsistentWhitespace` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description This rule is not enabled by default. The user needs to enable it through settings. ## Configuration ```powershell theme={null} Rules = @{ PSUseConsistentWhitespace = @{ Enable = $true CheckInnerBrace = $true CheckOpenBrace = $true CheckOpenParen = $true CheckOperator = $true CheckPipe = $true CheckPipeForRedundantWhitespace = $false CheckSeparator = $true CheckParameter = $false IgnoreAssignmentOperatorInsideHashTable = $false } } ``` ### Enable: bool (Default value is `$false`) Enable or disable the rule during ScriptAnalyzer invocation. ### CheckInnerBrace: bool (Default value is `$true`) Checks if there is a space after the opening brace and a space before the closing brace. E.g. `if ($true) { foo }` instead of `if ($true) {bar}`. ### CheckOpenBrace: bool (Default value is `$true`) Checks if there is a space between a keyword and its corresponding open brace. E.g. `foo { }` instead of `foo{ }`. If an open brace is preceded by an open parenthesis, then no space is required. ### CheckOpenParen: bool (Default value is `$true`) Checks if there is space between a keyword and its corresponding open parenthesis. E.g. `if (true)` instead of `if(true)`. ### CheckOperator: bool (Default value is `$true`) Checks if a binary or unary operator is surrounded on both sides by a space. E.g. `$x = 1` instead of `$x=1`. ### CheckSeparator: bool (Default value is `$true`) Checks if a comma or a semicolon is followed by a space. E.g. `@(1, 2, 3)` or `@{a = 1; b = 2}` instead of `@(1,2,3)` or `@{a = 1;b = 2}`. ### CheckPipe: bool (Default value is `$true`) Checks if a pipe is surrounded on both sides by a space but ignores redundant whitespace. E.g. `foo | bar` instead of `foo|bar`. ### CheckPipeForRedundantWhitespace : bool (Default value is `$false`) Checks if a pipe is surrounded by redundant whitespace (i.e. more than 1 whitespace). E.g. `foo | bar` instead of `foo | bar`. ### CheckParameter: bool (Default value is `$false` at the moment due to the setting being new) Checks if there is more than one space between parameters and values. E.g. `foo -bar $baz -bat` instead of `foo -bar $baz -bat`. This eliminates redundant whitespace that was probably added unintentionally. The rule does not check for whitespace between parameter and value when the colon syntax `-ParameterName:$ParameterValue` is used as some users prefer either 0 or 1 whitespace in this case. ### IgnoreAssignmentOperatorInsideHashTable: bool (Default value is `$false`) When `CheckOperator` is set, ignore whitespace around assignment operators within multi-line hash tables. Set this option to use the `AlignAssignmentStatement` rule and still check whitespace around operators everywhere else. ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseConsistentWhitespace](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseConsistentWhitespace.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseConstrainedLanguageMode Source: https://tally.wharflab.com/rules/powershell/PSUseConstrainedLanguageMode Use patterns compatible with Constrained Language Mode `powershell/PSUseConstrainedLanguageMode` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description This rule identifies PowerShell patterns that are restricted or not permitted in Constrained Language Mode (CLM). Constrained Language Mode is a PowerShell security feature that restricts: * .NET types that can be used * COM objects that can be instantiated * Commands that can be executed * Language features that can be used CLM is commonly used in: * Application Control environments (Application Control for Business, AppLocker) * Just Enough Administration (JEA) endpoints * Secure environments requiring additional PowerShell restrictions Digitally signed scripts from trusted publishers execute in Full Language Mode (FLM) even in CLM environments. The rule detects signature blocks (`# SIG # Begin signature block`) and adjusts checks accordingly. Most restrictions don't apply to signed scripts, but certain checks (dot-sourcing, parameter types, manifest best practices) are always enforced. > \[!IMPORTANT] > The rule performs a simple text check for signature blocks and does NOT validate signature > authenticity or certificate trust. Actual signature validation is performed by PowerShell at > runtime. ## Constrained Language Mode Restrictions ### Unsigned Scripts (Full CLM Checking) The following are flagged for unsigned scripts: 1. **Add-Type** - Code compilation not permitted 2. **Disallowed COM Objects** - Only Scripting.Dictionary, Scripting.FileSystemObject, VBScript.RegExp allowed 3. **Disallowed .NET Types** - Only \~70 allowed types (string, int, hashtable, pscredential, etc.) 4. **Type Constraints** - On parameters and variables 5. **Type Expressions** - Static type references like `[Type]::Method()` 6. **Type Casts** - Converting to disallowed types 7. **Member Invocations** - Methods/properties on disallowed types 8. **PowerShell Classes** - `class` keyword not permitted 9. **XAML/WPF** - Not permitted 10. **Invoke-Expression** - Restricted 11. **Dot-Sourcing** - May be restricted depending on the file being sourced 12. **Module Manifest Wildcards** - Wildcard exports not recommended 13. **Module Manifest .ps1 Files** - Script modules ending with .ps1 not allowed Always enforced, even for signed scripts ### Signed Scripts (Selective Checking) For scripts with signature blocks, only these are checked: * Dot-sourcing * Parameter type constraints * Module manifest wildcards (.psd1 files) * Module manifest script modules (.psd1 files) ## Configuration ### Basic Configuration ```powershell theme={null} @{ Rules = @{ PSUseConstrainedLanguageMode = @{ Enable = $true } } } ``` ### Parameters #### Enable: bool (Default value is `$false`) Enable or disable the rule during ScriptAnalyzer invocation. This rule is disabled by default because not all scripts need CLM compatibility. #### IgnoreSignatures: bool (Default value is `$false`) Control signature detection behavior: * `$false` (default): Automatically detect signatures. Signed scripts get selective checking, unsigned get full checking. * `$true`: Bypass signature detection. ALL scripts get full CLM checking regardless of signature status. ```powershell theme={null} @{ Rules = @{ PSUseConstrainedLanguageMode = @{ Enable = $true IgnoreSignatures = $true # Enforce full CLM compliance for all scripts } } } ``` Use `IgnoreSignatures = $true` when: * Auditing signed scripts for complete CLM compatibility * Preparing scripts for untrusted environments * Enforcing strict CLM compliance organization-wide * Development/testing to see all potential issues ## How to Fix ### Replace Add-Type Use allowed cmdlets or pre-compile assemblies. ### Replace Disallowed COM Objects Use only allowed COM objects (Scripting.Dictionary, Scripting.FileSystemObject, VBScript.RegExp) or PowerShell cmdlets. ### Replace Disallowed Types Use allowed type accelerators (`[string]`, `[int]`, `[hashtable]`, etc.) or allowed cmdlets instead of disallowed .NET types. ### Replace PowerShell Classes Use `New-Object PSObject` with `Add-Member` or hashtables instead of classes. > \[!IMPORTANT] > `[PSCustomObject]@{}` syntax is NOT allowed in CLM because it uses type casting. ### Avoid XAML Don't use WPF/XAML in CLM-compatible scripts. ### Replace Invoke-Expression Use direct execution (`&`) or safer alternatives. ### Replace Dot-Sourcing Use modules with Import-Module instead of dot-sourcing when possible. ### Fix Module Manifests * Replace wildcard exports (`*`) with explicit lists. * Use `.psm1` or `.dll` instead of `.ps1` for RootModule/NestedModules. * Don't use `ScriptsToProcess`. These scripts are loaded in the caller's scope and are blocked. ## Examples ### Example 1: Add-Type #### Wrong ```powershell theme={null} Add-Type -TypeDefinition @" public class Helper { public static string DoWork() { return "Done"; } } "@ ``` #### Correct ```powershell theme={null} # Code sign your scripts/modules using proper signing tools # (for example, Set-AuthenticodeSignature or external signing processes) # Use allowed cmdlets instead of Add-Type-defined types where possible # Or pre-compile, sign, and load the assembly (for example, via Add-Type -Path) ``` ### Example 2: COM Objects #### Wrong ```powershell theme={null} $excel = New-Object -ComObject Excel.Application ``` #### Correct ```powershell theme={null} # Use allowed COM object $dict = New-Object -ComObject Scripting.Dictionary # Or use PowerShell cmdlets Import-Excel -Path $file # From ImportExcel module ``` ### Example 3: Disallowed Types #### Wrong ```powershell theme={null} # Type constraint and member invocation flagged function Download-File { param([System.Net.WebClient]$Client) $Client.DownloadString($url) } # Type cast and method call flagged [System.Net.WebClient]$client = New-Object System.Net.WebClient $data = $client.DownloadData($url) ``` #### Correct ```powershell theme={null} # Use allowed cmdlets function Download-File { param([string]$Url) Invoke-WebRequest -Uri $Url } # Use allowed types function Process-Text { param([string]$Text) $upper = $Text.ToUpper() # String methods are allowed } ``` ### Example 4: PowerShell Classes #### Wrong ```powershell theme={null} class MyClass { [string]$Name [string]GetInfo() { return $this.Name } } # Also wrong - uses type cast $obj = [PSCustomObject]@{ Name = "Test" } ``` #### Correct ```powershell theme={null} # Option 1: New-Object PSObject with Add-Member $obj = New-Object PSObject -Property @{ Name = "Test" } $obj | Add-Member -MemberType ScriptMethod -Name GetInfo -Value { return $this.Name } Add-Member -InputObject $obj -NotePropertyMembers @{"Number" = 42} # Option 2: Hashtable $obj = @{ Name = "Test" Number = 42 } ``` ### Example 5: Module Manifests #### Wrong ```powershell theme={null} @{ ModuleVersion = '1.0.0' RootModule = 'MyModule.ps1' # .ps1 not recommended FunctionsToExport = '*' # Wildcard not recommended CmdletsToExport = '*' } ``` #### Correct ```powershell theme={null} @{ ModuleVersion = '1.0.0' RootModule = 'MyModule.psm1' # Use .psm1 or .dll FunctionsToExport = @( # Explicit list 'Get-MyFunction' 'Set-MyFunction' ) CmdletsToExport = @() } ``` ### Example 6: Array Types #### Wrong ```powershell theme={null} # Disallowed type in array param([System.Net.WebClient[]]$Clients) ``` #### Correct ```powershell theme={null} # Allowed types in arrays are fine param([string[]]$Names) param([int[]]$Numbers) param([hashtable[]]$Configuration) ``` ## Detailed Restrictions ### 1. Add-Type `Add-Type` allows compiling arbitrary C# code and isn't permitted in CLM. **Enforced For**: Unsigned scripts only ### 2. COM Objects Only three COM objects are allowed: * `Scripting.Dictionary` * `Scripting.FileSystemObject` * `VBScript.RegExp` All others (Excel.Application, WScript.Shell, etc.) are flagged. **Enforced For**: Unsigned scripts only ### 3. .NET Types Only \~70 allowed types including: * Primitives: `string`, `int`, `bool`, `byte`, `char`, `datetime`, `decimal`, `double`, etc. * Collections: `hashtable`, `array`, `arraylist` * PowerShell: `pscredential`, `psobject`, `securestring` * Utilities: `regex`, `guid`, `version`, `uri`, `xml` * Arrays: `string[]`, `int[][]`, etc. (array of any allowed type) The rule checks type usage in: * Parameter type constraints (**always enforced, even for signed scripts**) * Variable type constraints * New-Object -TypeName * Type expressions (`[Type]::Method()`) * Type casts (`[Type]$variable`) * Member invocations on typed variables **Enforced For**: Parameter constraints always; others unsigned only ### 4. PowerShell Classes The `class` keyword is not permitted. Use `New-Object PSObject` with `Add-Member` or hashtables. **Note**: `[PSCustomObject]@{}` is also not allowed because it uses type casting. **Enforced For**: Unsigned scripts only ### 5. XAML/WPF XAML and WPF are not permitted in CLM. **Enforced For**: Unsigned scripts only ### 6. Invoke-Expression `Invoke-Expression` is restricted in CLM. **Enforced For**: Unsigned scripts only ### 7. Dot-Sourcing Dot-sourcing (`. $PSScriptRoot\script.ps1`) may be restricted depending on source location. **Enforced For**: ALL scripts (unsigned and signed) ### 8. Module Manifest Best Practices #### Wildcard Exports Don't use `*` in: `FunctionsToExport`, `CmdletsToExport`, `AliasesToExport`, `VariablesToExport` Use explicit lists for security and clarity. **Enforced For**: ALL .psd1 files (unsigned and signed) #### Script Module Files Don't use `.ps1` files in: `RootModule`, `ModuleToProcess`, `NestedModules` Use `.psm1` (script modules) or `.dll` (binary modules) for better performance and compatibility. **Enforced For**: ALL .psd1 files (unsigned and signed) ## More Information * [About Language Modes][01] * [PowerShell Constrained Language Mode and the Dot-Source Operator][02] * [PowerShell Constrained Language Mode][03] * [PowerShell Module Function Export in Constrained Language][04] [01]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_language_modes [02]: https://devblogs.microsoft.com/powershell/powershell-constrained-language-mode-and-the-dot-source-operator/ [03]: https://devblogs.microsoft.com/powershell/powershell-constrained-language-mode/ [04]: https://devblogs.microsoft.com/powershell/powershell-module-function-export-in-constrained-language/ ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseConstrainedLanguageMode](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseConstrainedLanguageMode.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseCorrectCasing Source: https://tally.wharflab.com/rules/powershell/PSUseCorrectCasing Use exact casing of cmdlet/function/parameter name. `powershell/PSUseCorrectCasing` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Information | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description This is a style/formatting rule. PowerShell is case insensitive wherever possible, so the casing of cmdlet names, parameters, keywords and operators doesn't matter. This rule nonetheless ensures consistent casing for clarity and readability. Using lowercase keywords helps distinguish them from commands. Using lowercase operators helps distinguish them from parameters. ## How * Use exact casing for type names. * Use exact casing of the cmdlet and its parameters. * Use lowercase for language keywords and operators. ## Configuration ```powershell theme={null} Rules = @{ PSUseCorrectCasing = @{ Enable = $true CheckCommands = $true CheckKeyword = $true CheckOperator = $true } } ``` ## Parameters ### Enable: bool (Default value is `$false`) Enable or disable the rule during ScriptAnalyzer invocation. ### CheckCommands: bool (Default value is `$true`) If true, require the case of all command and parameter names to match their canonical casing. ### CheckKeyword: bool (Default value is `$true`) If true, require the case of all keywords to be lowercase. ### CheckOperator: bool (Default value is `$true`) If true, require the case of all operators to be lowercase. For example: `-eq`, `-ne`, `-gt` ## Examples ### Wrong way ```powershell theme={null} ForEach ($file in Get-childitem -Recurse) { $file.Extension -EQ '.txt' } invoke-command { 'foo' } -runasadministrator ``` ### Correct way ```powershell theme={null} foreach ($file in Get-ChildItem -Recurse) { $file.Extension -eq '.txt' } Invoke-Command { 'foo' } -RunAsAdministrator ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseCorrectCasing](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseCorrectCasing.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseDeclaredVarsMoreThanAssignments Source: https://tally.wharflab.com/rules/powershell/PSUseDeclaredVarsMoreThanAssignments Extra Variables `powershell/PSUseDeclaredVarsMoreThanAssignments` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Variables that are assigned but not used are not needed. For this rule, the variable must be used within the same scriptblock that it was declared or it won't be considered to be 'used'. ## How Remove the variables that are declared but not used. ## Examples ### Problematic code ```powershell theme={null} function Test { $declaredVar = 'Declared just for fun' $declaredVar2 = 'Not used' Write-Output $declaredVar } ``` ### Correct code ```powershell theme={null} function Test { $declaredVar = 'Declared just for fun' Write-Output $declaredVar } ``` ### Special cases The following examples trigger the **PSUseDeclaredVarsMoreThanAssignments** warning. This behavior is a limitation of the rule. There is no way to avoid these false positive warnings. In this case, the warning is triggered because `$bar` is not used within the scriptblock where it was defined. ```powershell theme={null} $foo | ForEach-Object { if ($_ -eq $false) { $bar = $true } } if($bar){ Write-Host 'Collection contained a false case.' } ``` In the next example, the warning is triggered because `$errResult` isn't recognized as being used in the `Write-Host` command. ```powershell theme={null} $errResult = $null Write-Host 'Ugh:' -ErrorVariable errResult ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseDeclaredVarsMoreThanAssignments](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseDeclaredVarsMoreThanAssignments.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseLiteralInitializerForHashtable Source: https://tally.wharflab.com/rules/powershell/PSUseLiteralInitializerForHashtable Create hashtables with literal initializers `powershell/PSUseLiteralInitializerForHashtable` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Creating a hashtable using `[hashtable]::new()` or `New-Object -TypeName hashtable` without passing a `IEqualityComparer` object to the constructor creates a hashtable where the keys are looked-up in a case-sensitive manner. However, PowerShell is case-insensitive in nature and it is best to create hashtables with case-insensitive key look-up. This rule is intended to warn the author of the case-sensitive nature of the hashtable when created using the `new` method or the `New-Object` cmdlet. ## How to Fix Create the hashtable using a literal hashtable expression. ## Examples ### Problematic code (`[hashtable]::new()`) ```powershell theme={null} $hashtable = [hashtable]::new() ``` ### Problematic code (`New-Object`) ```powershell theme={null} $hashtable = New-Object -TypeName hashtable ``` ### Correct code ```powershell theme={null} $hashtable = @{} ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseLiteralInitializerForHashtable](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseLiteralInitializerForHashtable.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseOutputTypeCorrectly Source: https://tally.wharflab.com/rules/powershell/PSUseOutputTypeCorrectly Use OutputType Correctly `powershell/PSUseOutputTypeCorrectly` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Information | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSUseOutputTypeCorrectly"]` or by setting `rules.powershell.PSUseOutputTypeCorrectly.severity = "warning"` in `.tally.toml`. ## Description A command should return the same type as declared in `OutputType`. You can get more details by running `Get-Help about_Functions_OutputTypeAttribute` command in PowerShell. ## How Specify that the OutputType attribute lists and the types returned in the cmdlet match. ## Examples ### Problematic code ```powershell theme={null} function Get-Foo { [CmdletBinding()] [OutputType([String])] Param( ) return 4 } ``` ### Correct code ```powershell theme={null} function Get-Foo { [CmdletBinding()] [OutputType([String])] Param( ) return 'four' } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseOutputTypeCorrectly](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseOutputTypeCorrectly.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUsePSCredentialType Source: https://tally.wharflab.com/rules/powershell/PSUsePSCredentialType Use PSCredential type. `powershell/PSUsePSCredentialType` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description If the cmdlet or function has a **Credential** parameter, the parameter must accept the **PSCredential** type. ## How Change the **Credential** parameter's type to be **PSCredential**. ## Examples ### Problematic code ```powershell theme={null} function Credential([String]$Credential) { ... } ``` ### Correct code ```powershell theme={null} function Credential([PSCredential]$Credential) { ... } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UsePSCredentialType](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UsePSCredentialType.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseProcessBlockForPipelineCommand Source: https://tally.wharflab.com/rules/powershell/PSUseProcessBlockForPipelineCommand Use process block for command that accepts input from pipeline. `powershell/PSUseProcessBlockForPipelineCommand` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSUseProcessBlockForPipelineCommand"]` or by setting `rules.powershell.PSUseProcessBlockForPipelineCommand.severity = "warning"` in `.tally.toml`. ## Description Functions that support pipeline input should always handle parameter input in a process block. Unexpected behavior can result if input is handled directly in the body of a function where parameters declare pipeline support. ## Examples ### Problematic code ```powershell theme={null} Function Get-Number { [CmdletBinding()] Param( [Parameter(ValueFromPipeline)] [int] $Number ) $Number } ``` #### Result ```text theme={null} PS C:\> 1..5 | Get-Number 5 ``` ### Correct code ```powershell theme={null} Function Get-Number { [CmdletBinding()] Param( [Parameter(ValueFromPipeline)] [int] $Number ) process { $Number } } ``` #### Result ```text theme={null} PS C:\> 1..5 | Get-Number 1 2 3 4 5 ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseProcessBlockForPipelineCommand](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseProcessBlockForPipelineCommand.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseShouldProcessForStateChangingFunctions Source: https://tally.wharflab.com/rules/powershell/PSUseShouldProcessForStateChangingFunctions Use ShouldProcess For State Changing Functions `powershell/PSUseShouldProcessForStateChangingFunctions` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSUseShouldProcessForStateChangingFunctions"]` or by setting `rules.powershell.PSUseShouldProcessForStateChangingFunctions.severity = "warning"` in `.tally.toml`. ## Description Functions whose verbs change system state should support `ShouldProcess`. To enable the `ShouldProcess` feature, set the `SupportsShouldProcess` argument in the `CmdletBinding` attribute. The `SupportsShouldProcess` argument adds **Confirm** and **WhatIf** parameters to the function. The **Confirm** parameter prompts the user before it runs the command on each object in the pipeline. The **WhatIf** parameter lists the changes that the command would make, instead of running the command. Verbs that should support `ShouldProcess`: * `New` * `Set` * `Remove` * `Start` * `Stop` * `Restart` * `Reset` * `Update` ## How Include the `SupportsShouldProcess` argument in the `CmdletBinding` attribute. ## Examples ### Problematic code ```powershell theme={null} function Set-ServiceObject { [CmdletBinding()] param ( [string] $Parameter1 ) ... } ``` ### Correct code ```powershell theme={null} function Set-ServiceObject { [CmdletBinding(SupportsShouldProcess = $true)] param ( [string] $Parameter1 ) ... } ``` ## More information * [about\_Functions\_CmdletBindingAttribute][01] * [Everything you wanted to know about ShouldProcess][04] * [Required Development Guidelines][03] * [Requesting Confirmation from Cmdlets][02] [01]: /powershell/module/microsoft.powershell.core/about/about_functions_cmdletbindingattribute [02]: /powershell/scripting/developer/cmdlet/requesting-confirmation-from-cmdlets [03]: /powershell/scripting/developer/cmdlet/required-development-guidelines#support-confirmation-requests-rd04 [04]: /powershell/scripting/learn/deep-dives/everything-about-shouldprocess ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseShouldProcessForStateChangingFunctions](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseShouldProcessForStateChangingFunctions.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseSingleValueFromPipelineParameter Source: https://tally.wharflab.com/rules/powershell/PSUseSingleValueFromPipelineParameter Use at most a single ValueFromPipeline parameter per parameter set. `powershell/PSUseSingleValueFromPipelineParameter` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description Parameter sets should have at most one parameter marked as `ValueFromPipeline = true`. This rule identifies functions where multiple parameters within the same parameter set have `ValueFromPipeline` set to `true` (either explicitly or implicitly). ## How Ensure that only one parameter per parameter set accepts pipeline input by value. If you need multiple parameters to accept different types of pipeline input, use separate parameter sets. ## Examples ### Problematic code ```powershell theme={null} function Process-Data { [CmdletBinding()] param( [Parameter(ValueFromPipeline)] [string] $InputData, [Parameter(ValueFromPipeline)] [string] $ProcessingMode ) process { Write-Output "$ProcessingMode`: $InputData" } } ``` ### Correct code ```powershell theme={null} function Process-Data { [CmdletBinding()] param( [Parameter(ValueFromPipeline)] [string] $InputData, [Parameter(Mandatory)] [string] $ProcessingMode ) process { Write-Output "$ProcessingMode`: $InputData" } } ``` ## Suppression To suppress this rule for a specific parameter set, use the `SuppressMessage` attribute with the parameter set name: ```powershell theme={null} function Process-Data { [Diagnostics.CodeAnalysis.SuppressMessage('PSUseSingleValueFromPipelineParameter', 'MyParameterSet')] [CmdletBinding()] param( [Parameter(ValueFromPipeline, ParameterSetName='MyParameterSet')] [string] $InputData, [Parameter(ValueFromPipeline, ParameterSetName='MyParameterSet')] [string] $ProcessingMode ) process { Write-Output "$ProcessingMode`: $InputData" } } ``` For the default parameter set, use `'default'` as the suppression target: ```powershell theme={null} [Diagnostics.CodeAnalysis.SuppressMessage('PSUseSingleValueFromPipelineParameter', 'default')] ``` ## Notes * This rule applies to both explicit `ValueFromPipeline = $true` and implicit `ValueFromPipeline` (which is the same as using `= $true`) * Parameters with `ValueFromPipeline=$false` are not flagged by this rule * The rule correctly handles the default parameter set (`__AllParameterSets`) and named parameter sets * Different parameter sets can each have their own single `ValueFromPipeline` parameter without triggering this rule ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseSingleValueFromPipelineParameter](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseSingleValueFromPipelineParameter.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseSingularNouns Source: https://tally.wharflab.com/rules/powershell/PSUseSingularNouns Cmdlet Singular Noun `powershell/PSUseSingularNouns` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSUseSingularNouns"]` or by setting `rules.powershell.PSUseSingularNouns.severity = "warning"` in `.tally.toml`. ## Description PowerShell team best practices state cmdlets should use singular nouns and not plurals. Suppression allows you to suppress the rule for specific function names. For example: ```text theme={null} function Get-Elements { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', 'Get-Elements')] Param() } ``` ## Configuration ```powershell theme={null} Rules = @{ PSUseSingularNouns = @{ Enable = $true NounAllowList = 'Data', 'Windows', 'Foos' } } ``` ### Parameters * `Enable`: `bool` (Default value is `$true`) Enable or disable the rule during ScriptAnalyzer invocation. * `NounAllowList`: `string[]` (Default value is `{'Data', 'Windows'}`) Commands to be excluded from this rule. `Data` and `Windows` are common false positives and are excluded by default. ## How Change plurals to singular. ## Examples ### Problematic code ```powershell theme={null} function Get-Files { ... } ``` ### Correct code ```powershell theme={null} function Get-File { ... } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseSingularNouns](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseSingularNouns.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseSupportsShouldProcess Source: https://tally.wharflab.com/rules/powershell/PSUseSupportsShouldProcess Use SupportsShouldProcess `powershell/PSUseSupportsShouldProcess` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default for Dockerfile `RUN` snippets because a `RUN` executes statements inline rather than defining cmdlets, parameters, or modules with external callers. Re-enable it with `include = ["powershell/PSUseSupportsShouldProcess"]` or by setting `rules.powershell.PSUseSupportsShouldProcess.severity = "warning"` in `.tally.toml`. ## Description This rule discourages manual declaration of `WhatIf` and `Confirm` parameters in a function/cmdlet. These parameters are, however, provided automatically when a function declares a `CmdletBinding` attribute with `SupportsShouldProcess` as its named argument. Using `SupportsShouldProcess` not only provides these parameters but also some generic functionality that allows the function/cmdlet authors to provide the desired interactive experience while using the cmdlet. ## Examples ### Problematic code ```powershell theme={null} function foo { param( $param1, $Confirm, $WhatIf ) } ``` ### Correct code ```powershell theme={null} function foo { [CmdletBinding(SupportsShouldProcess)] param( $param1 ) } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseSupportsShouldProcess](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseSupportsShouldProcess.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseToExportFieldsInManifest Source: https://tally.wharflab.com/rules/powershell/PSUseToExportFieldsInManifest Use the *ToExport module manifest fields. `powershell/PSUseToExportFieldsInManifest` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default because Dockerfiles do not ship PowerShell module manifests (`.psd1` files) — this rule targets module-authoring artifacts that are out of scope for container builds. Re-enable it with `include = ["powershell/PSUseToExportFieldsInManifest"]` or by setting `rules.powershell.PSUseToExportFieldsInManifest.severity = "warning"` in `.tally.toml`. ## Description To improve the performance of module auto-discovery, module manifests should not use wildcards (`'*'`) or null (`$null`) in the following entries: * `AliasesToExport` * `CmdletsToExport` * `FunctionsToExport` * `VariablesToExport` Using wildcards or null causes PowerShell to perform expensive work to analyze a module during module auto-discovery. ## How Use an explicit list in the entries. ## Example 1 Suppose there are no functions in your module to export. Then, ### Problematic code ```powershell theme={null} FunctionsToExport = $null ``` ### Correct code ```powershell theme={null} FunctionsToExport = @() ``` ## Example 2 Suppose there are only two functions in your module, `Get-Foo` and `Set-Foo` that you want to export. Then, ### Problematic code ```powershell theme={null} FunctionsToExport = '*' ``` ### Correct code ```powershell theme={null} FunctionsToExport = @(Get-Foo, Set-Foo) ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseToExportFieldsInManifest](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseToExportFieldsInManifest.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseUTF8EncodingForHelpFile Source: https://tally.wharflab.com/rules/powershell/PSUseUTF8EncodingForHelpFile Use UTF8 Encoding For Help File `powershell/PSUseUTF8EncodingForHelpFile` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ----------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Default | Disabled in tally | | Auto-fix | No | tally disables this rule by default because Dockerfile `RUN` bodies are inline strings, not files on disk with encoding metadata. Re-enable it with `include = ["powershell/PSUseUTF8EncodingForHelpFile"]` or by setting `rules.powershell.PSUseUTF8EncodingForHelpFile.severity = "warning"` in `.tally.toml`. ## Description Check that an `about_` help file uses UTF-8 encoding. The filename must start with `about_` and end with `.help.txt`. The rule uses the **CurrentEncoding** property of the **StreamReader** class to determine the encoding of the file. ## How For PowerShell commands that write to files, ensure that you set the encoding parameter to `utf8`, `utf8BOM`, or `utf8NoBOM`. When you create a help file using a text editor, ensure that the editor is configured to save the file in a UTF8 format. Consult the documentation for your text editor for instructions on how to save files with a specific encoding. ## Further reading For more information, see the following articles: * `System.IO.StreamReader` * [about\_Character\_Encoding](/powershell/module/microsoft.powershell.core/about/about_character_encoding) * [Set-Content](https://learn.microsoft.com/powershell/module/microsoft.powershell.management/set-content) * [Understanding file encoding in VS Code and PowerShell](/powershell/scripting/dev-cross-plat/vscode/understanding-file-encoding) ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseUTF8EncodingForHelpFile](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseUTF8EncodingForHelpFile.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PSUseUsingScopeModifierInNewRunspaces Source: https://tally.wharflab.com/rules/powershell/PSUseUsingScopeModifierInNewRunspaces Use 'Using:' scope modifier in RunSpace ScriptBlocks `powershell/PSUseUsingScopeModifierInNewRunspaces` is a PSScriptAnalyzer diagnostic emitted by tally for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | PSScriptAnalyzer | | Auto-fix | No | ## Description If a scriptblock is intended to be run in a new runspace, variables inside it should use the `$using:` scope modifier, or be initialized within the scriptblock. This applies to: * `Invoke-Command`- Only with the **ComputerName** or **Session** parameter. * `Workflow { InlineScript {} }` * `Foreach-Object` - Only with the **Parallel** parameter * `Start-Job` * `Start-ThreadJob` * The `Script` resource in DSC configurations, specifically for the `GetScript`, `TestScript` and `SetScript` properties. ## How to Fix Within the ScriptBlock, instead of just using a variable from the parent scope, you have to add the `using:` scope modifier to it. ## Examples ### Problematic code ```powershell theme={null} $var = 'foo' 1..2 | ForEach-Object -Parallel { $var } ``` ### Correct code ```powershell theme={null} $var = 'foo' 1..2 | ForEach-Object -Parallel { $using:var } ``` ## More correct examples ```powershell theme={null} $bar = 'bar' Invoke-Command -ComputerName 'foo' -ScriptBlock { $using:bar } ``` ```powershell theme={null} $bar = 'bar' $s = New-PSSession -ComputerName 'foo' Invoke-Command -Session $s -ScriptBlock { $using:bar } ``` ```powershell theme={null} # Remark: Workflow is supported on Windows PowerShell only Workflow { $foo = 'foo' InlineScript { $using:foo } } ``` ```powershell theme={null} $foo = 'foo' Start-ThreadJob -ScriptBlock { $using:foo } Start-Job -ScriptBlock {$using:foo } ``` ## Source This rule documentation is adapted from Microsoft's PSScriptAnalyzer documentation for [UseUsingScopeModifierInNewRunspaces](https://github.com/MicrosoftDocs/PowerShell-Docs-Modules/blob/main/reference/docs-conceptual/PSScriptAnalyzer/Rules/UseUsingScopeModifierInNewRunspaces.md), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # powershell/PowerShell Source: https://tally.wharflab.com/rules/powershell/PowerShell Runs PowerShell script diagnostics for PowerShell snippets embedded in Dockerfiles. Runs PowerShell script diagnostics for PowerShell snippets embedded in Dockerfiles. | Property | Value | | -------- | ------------------------------------------------------ | | Severity | Warning | | Category | Best Practices | | Default | Enabled, gated by slow checks | | Auto-fix | Suggestions when PSScriptAnalyzer provides corrections | ## Description tally analyzes Dockerfile `RUN` instructions that execute PowerShell, including stages with `SHELL ["pwsh", "-Command"]`, explicit `RUN pwsh -Command ...` wrappers, and PowerShell heredoc bodies. Diagnostics are reported in the `powershell/*` namespace using the upstream PowerShell rule name, such as `powershell/PSAvoidUsingWriteHost`. Each concrete diagnostic links to its own tally documentation page, for example [`powershell/PSAvoidUsingWriteHost`](/rules/powershell/PSAvoidUsingWriteHost). The PowerShell analyzer is enabled by default, but it is gated by slow checks. With the default `slow-checks.mode = "auto"`, tally runs it locally and skips it in CI; use `--slow-checks=on` when CI should run PowerShell analysis, or `--slow-checks=off` to skip it explicitly. The sidecar still starts lazily. Dockerfiles without PowerShell snippets do not start `pwsh`, even when slow checks are enabled. Selecting `powershell/PowerShell`, `powershell/*`, or a specific rule such as `powershell/PSAvoidUsingWriteHost` controls rule filtering, but it does not bypass the slow-check gate. Rule-specific options under `rules.powershell.` are forwarded to PSScriptAnalyzer as `Settings.Rules` entries. This supports upstream options such as `Enable`, `TargetProfiles`, and compatibility profile settings documented on the concrete `powershell/*` rule pages. When an upstream PSScriptAnalyzer diagnostic includes suggested corrections, tally exposes them as normal fix suggestions. These fixes are only attached when the PowerShell snippet can be mapped back to precise Dockerfile source ranges. ## Default-disabled PSScriptAnalyzer rules A subset of PSScriptAnalyzer rules is targeted at long-lived script reusability, function or module authoring, manifest authoring, or Desired State Configuration — concerns that do not apply to a one-shot Dockerfile `RUN`. tally ships with these rules disabled by default so the diagnostics you see are correctness, security, and compatibility findings. Each affected rule's documentation page has a `Default: Disabled in tally` row and a note explaining why. To re-enable a default-disabled rule for your project, opt in explicitly: ```toml theme={null} # .tally.toml [rules] include = ["powershell/PSAvoidUsingWriteHost"] # Or per-rule: [rules.powershell.PSAvoidUsingWriteHost] severity = "warning" ``` To re-enable the entire default-disabled set, use the namespace wildcard: `include = ["powershell/*"]`. The engine selector `include = ["powershell/PowerShell"]` only toggles the analyzer engine and does **not** override per-rule defaults. ## Requirements * PowerShell 7 (`pwsh`) must be available on `PATH`. * Windows PowerShell 5.1 (`powershell.exe`) is not supported as an analyzer host; use PowerShell 7+ (`pwsh`). * The first PowerShell analyzer run installs tally's pinned `PSScriptAnalyzer` release automatically for that `pwsh` environment when the tested version is missing. Use `TALLY_POWERSHELL=/path/to/pwsh` when `pwsh` is not on `PATH` or when tally should use a specific PowerShell installation. The automatic install uses the selected `pwsh` environment's PowerShell package tooling (`Install-PSResource` when available, otherwise `Install-Module`) and installs tally's pinned `PSScriptAnalyzer` release to `CurrentUser` scope. If the host has no network access or blocks PowerShell Gallery, preinstall the version pinned by your tally release and rerun tally. Cold installation downloads the PSScriptAnalyzer module and can take more than a few seconds depending on network speed. If the bootstrap is still running after 3 seconds, tally writes a progress note to stderr and repeats it periodically until the sidecar is ready. Set `TALLY_POWERSHELL_PROGRESS=0` to silence these notes. ## Examples ### Problematic code ```dockerfile theme={null} FROM mcr.microsoft.com/powershell:ubuntu-22.04 SHELL ["pwsh", "-Command"] RUN Write-Host hi ``` ### Reported diagnostic ```text theme={null} powershell/PSAvoidUsingWriteHost ``` ### Disable a specific PowerShell rule ```bash theme={null} tally lint --ignore powershell/PSAvoidUsingWriteHost Dockerfile ``` ### Disable PowerShell script analysis ```bash theme={null} tally lint --ignore powershell/* Dockerfile ``` ## Reference * [PSScriptAnalyzer rules](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/rules/readme?view=ps-modules) # shellcheck/SC1040 Source: https://tally.wharflab.com/rules/shellcheck/SC1040 When using `<<-`, the heredoc terminator may only be indented with tabs. When using `<<-`, the heredoc terminator may only be indented with tabs. | Property | Value | | -------- | ---------------------------- | | Severity | Error | | Category | Best Practices | | Default | Enabled (via `shellcheck/*`) | | Auto-fix | Yes (`--fix`, safe) | ## Description The `<<-` heredoc form strips leading **tabs** from heredoc lines. Leading spaces before the terminator are invalid and can prevent correct heredoc termination. tally reports this as `shellcheck/SC1040` and provides an auto-fix that removes only the extra leading spaces from the terminator indentation. ## Examples ### Problematic code ```dockerfile theme={null} RUN <<'SCRIPT' cat <<-EOF hello EOF EOF SCRIPT ``` ### Correct code ```dockerfile theme={null} RUN <<'SCRIPT' cat <<-EOF hello EOF EOF SCRIPT ``` ## Auto-fix The fix is minimal: * removes only leading space runs in the offending terminator line * preserves tab indentation * emits narrow edits so other fixes can still apply to the same script ```bash theme={null} tally lint --fix --select shellcheck/SC1040 Dockerfile ``` ## Reference * [ShellCheck SC1040](https://www.shellcheck.net/wiki/SC1040) # tally/circular-stage-deps Source: https://tally.wharflab.com/rules/tally/circular-stage-deps Detects circular dependencies between build stages. Detects circular dependencies between build stages. | Property | Value | | -------- | ----------- | | Severity | Error | | Category | Correctness | | Default | Enabled | ## Description Detects cycles in the stage dependency graph. A cycle occurs when stages form mutual dependencies through `COPY --from=`, `RUN --mount from=`, or `FROM ` references. Circular dependencies always cause build failures because no stage in the cycle can finish building before the others — each waits for output from another. Common causes: * A refactoring accidentally swaps stage references * An AI-generated patch links stages in both directions * A copy-paste introduces a forward `COPY --from` that mirrors an existing backward reference ## Examples ### Bad ```dockerfile theme={null} # builder copies from runtime, runtime copies from builder → cycle FROM golang:1.22-alpine AS builder COPY --from=runtime /usr/local/bin/tini /usr/local/bin/tini RUN go build -o /app ./cmd/server FROM alpine:3.19 AS runtime COPY --from=builder /app /usr/local/bin/app RUN apk add --no-cache ca-certificates FROM runtime ENTRYPOINT ["/usr/local/bin/app"] ``` ### Good ```dockerfile theme={null} # Dependencies flow in one direction: deps → builder → runtime FROM golang:1.22-alpine AS builder RUN go build -o /app ./cmd/server FROM alpine:3.19 AS runtime RUN apk add --no-cache ca-certificates COPY --from=builder /app /usr/local/bin/app FROM runtime ENTRYPOINT ["/usr/local/bin/app"] ``` ## Configuration ```toml theme={null} [rules.tally.circular-stage-deps] severity = "error" # Options: "off", "error", "warning", "info", "style" ``` # tally/consistent-indentation Source: https://tally.wharflab.com/rules/tally/consistent-indentation Enforces consistent indentation for Dockerfile build stages. Enforces consistent indentation for Dockerfile build stages. | Property | Value | | -------- | ------------------ | | Severity | Style | | Category | Style | | Default | Off (experimental) | | Auto-fix | Yes (safe) | ## Description Enforces consistent indentation to visually separate build stages in multi-stage Dockerfiles. This rule always uses **tabs** for indentation. **Behavior depends on the number of stages:** * **Multi-stage** (2+ FROM instructions): Commands within each stage must be indented with 1 tab. FROM lines remain at column 0. * **Single-stage** (1 FROM instruction): All indentation is removed — tabs, spaces, or any mix. Since there is no stage structure to communicate, indenting commands adds noise. The auto-fix strips all leading whitespace from every instruction. ### Why tabs only? Docker heredoc syntax (`<<-`) strips **leading tabs** from body lines. Spaces have no equivalent shell whitespace treatment — using them for indentation would corrupt heredoc content when `<<-` is applied. Because this rule must convert `<<` to `<<-` when adding indentation to heredoc instructions, only tabs produce correct results. ```dockerfile theme={null} FROM alpine:3.20 COPY <<-EOF /etc/config key=value other=setting EOF ``` With spaces, `<<-` cannot strip indentation, so the content would retain unwanted leading whitespace. ## Companion editor configuration Use [EditorConfig integration](/integrations/editorconfig) to configure IDEs and editors with the same tab indentation style recommended for Dockerfiles and Containerfiles. ### Multi-stage (indentation required) ```dockerfile theme={null} FROM golang:1.23 AS builder WORKDIR /src COPY . . RUN go build -o /app FROM alpine:3.20 COPY --from=builder /app /usr/local/bin/app ENTRYPOINT ["app"] ``` ### Single-stage (no indentation) ```dockerfile theme={null} FROM alpine:3.20 RUN apk add --no-cache curl COPY . /app CMD ["./app"] ``` ## Examples ### Bad (multi-stage without indentation) ```dockerfile theme={null} FROM golang:1.23 AS builder WORKDIR /src RUN go build -o /app FROM alpine:3.20 COPY --from=builder /app /app ``` ### Good (multi-stage with tab indentation) ```dockerfile theme={null} FROM golang:1.23 AS builder WORKDIR /src RUN go build -o /app FROM alpine:3.20 COPY --from=builder /app /app ``` ### Bad (single-stage with indentation) In a single-stage Dockerfile, indentation is unnecessary and will be removed by `--fix`: ```dockerfile theme={null} # Before (violation: unexpected indentation) FROM alpine:3.20 RUN apk add curl COPY . /app # After --fix (indentation removed) FROM alpine:3.20 RUN apk add curl COPY . /app ``` ## Configuration Enable the rule (no configurable options — tabs are always used): ```toml theme={null} [rules.tally.consistent-indentation] severity = "style" ``` ## Auto-fix This rule provides safe auto-fixes that adjust indentation: * **Multi-stage**: Adds 1 tab indentation to commands within stages * **Single-stage**: Removes all leading whitespace (tabs and spaces) from commands * **Style correction**: Replaces wrong indent characters (e.g., spaces to tabs) * **Heredoc `<<-` conversion**: When tab indentation is applied to a heredoc instruction (`RUN <` ancestry chain. If a parent stage sets a non-root `USER` that flows into a child stage via `FROM`, `COPY`/`ADD` in the child stage without `--chown` will trigger the rule. ## Relationship to other rules | | `tally/copy-after-user-without-chown` | `tally/prefer-copy-chmod` | | ---------- | -------------------------------------------- | -------------------------- | | Fires when | COPY/ADD after non-root USER without --chown | COPY followed by RUN chmod | | Fix | Adds `--chown=` or moves USER | Merges into `COPY --chmod` | | Scope | All stages | All stages | Both rules can fire on the same `COPY` instruction. Their fixes compose correctly: the result is `COPY --chown=user --chmod=mode file /dest`. ## Windows stages On Windows containers `--chown` is silently ignored (see [`tally/windows/no-chown-flag`](./windows/no-chown-flag)), so the "add `--chown`" fix is suppressed. The rule still fires — the ownership confusion is real — but only the "move `USER`" rearrangement fix is offered. ## Auto-fix Two fix alternatives are offered: 1. **Add `--chown=`** (preferred, safe): inserts `--chown=` to match the active `USER`, fixing the ownership mismatch directly. 2. **Move `USER` after COPY/ADD** (safe): relocates the `USER` instruction to just before the first `RUN` or `WORKDIR` that follows. This is a semantic no-op because `COPY`/`ADD` ownership is always `root:root` regardless of `USER`. It clarifies that `USER` only affects `RUN`, `WORKDIR`, and runtime identity. This alternative is only offered when no `RUN` or `WORKDIR` exists between the `USER` and the `COPY`/`ADD`. ## References * [Dockerfile reference -- USER](https://docs.docker.com/reference/dockerfile/#user) * [Dockerfile reference -- COPY --chown](https://docs.docker.com/reference/dockerfile/#copy---chown) * [Dockerfile reference -- ADD --chown](https://docs.docker.com/reference/dockerfile/#add---chown) * [Docker Blog -- Understanding the Docker USER Instruction](https://www.docker.com/blog/understanding-the-docker-user-instruction/) ## Examples ### Bad ```dockerfile theme={null} # COPY after USER without --chown: files are root-owned FROM ubuntu:22.04 RUN useradd -r appuser USER appuser COPY app /app CMD ["/app"] ``` ```dockerfile theme={null} # ADD after USER without --chown FROM ubuntu:22.04 USER 1000:1000 ADD config.tar.gz /etc/app/ RUN setup.sh ``` ### Good ```dockerfile theme={null} # Explicit --chown matches the active USER FROM ubuntu:22.04 RUN useradd -r appuser USER appuser COPY --chown=appuser app /app CMD ["/app"] ``` ```dockerfile theme={null} # USER placed after COPY, before RUN (clarifies intent) FROM ubuntu:22.04 RUN useradd -r appuser COPY app /app USER appuser RUN setup.sh CMD ["/app"] ``` ```dockerfile theme={null} # Ownership managed via RUN chown (suppressed) FROM ubuntu:22.04 RUN useradd -r appuser USER appuser COPY app /app RUN chown -R appuser:appuser /app CMD ["/app"] ``` ```dockerfile theme={null} # Numeric UID with --chown FROM ubuntu:22.04 USER 1000:1000 COPY --chown=1000:1000 app /app CMD ["/app"] ``` ## Configuration ```toml theme={null} [rules.tally.copy-after-user-without-chown] severity = "warning" # Options: "off", "error", "warning", "info", "style" ``` # tally/copy-from-empty-scratch-stage Source: https://tally.wharflab.com/rules/tally/copy-from-empty-scratch-stage Detects COPY --from referencing a scratch stage with no file-producing instructions. Detects COPY --from referencing a scratch stage with no file-producing instructions. | Property | Value | | -------- | ----------- | | Severity | Error | | Category | Correctness | | Default | Enabled | ## Description Detects `COPY --from=` instructions where the source stage is `FROM scratch` and contains no `ADD`, `COPY`, or `RUN` instructions. Since scratch stages start with an empty filesystem, any `COPY --from` referencing such a stage is guaranteed to fail at build time. Common causes: * A stage was renamed or deleted during a refactor, leaving an empty placeholder * An AI patch accidentally removed the instructions that populated the stage * A `COPY`/`RUN` was moved to a different stage but the `COPY --from` reference wasn't updated Instructions like `ENV`, `LABEL`, `EXPOSE`, `WORKDIR`, and `USER` do not produce filesystem content and are not considered file-producing for this check. ## Examples ### Bad ```dockerfile theme={null} # "artifacts" stage is scratch with no file-producing instructions FROM scratch AS artifacts FROM alpine:3.19 COPY --from=artifacts /out/app /usr/local/bin/app ``` ### Good ```dockerfile theme={null} FROM golang:1.22 AS builder RUN go build -o /out/app ./... # "artifacts" stage has a COPY that populates it FROM scratch AS artifacts COPY --from=builder /out/app /out/app FROM alpine:3.19 COPY --from=artifacts /out/app /usr/local/bin/app ``` ## Related rules * [`tally/shell-run-in-scratch`](./shell-run-in-scratch) — a scratch stage with only a shell-form `RUN` is not considered empty by this rule (any `RUN` counts as file-producing). The `shell-run-in-scratch` rule warns about the failing `RUN` instead. If the user removes that `RUN`, this rule will then fire on downstream `COPY --from` references. ## Configuration ```toml theme={null} [rules.tally.copy-from-empty-scratch-stage] severity = "error" # Options: "off", "error", "warning", "info", "style" ``` # tally/curl-should-follow-redirects Source: https://tally.wharflab.com/rules/tally/curl-should-follow-redirects curl commands should include `--location` (or `--follow`) to follow HTTP redirects. curl commands should include `--location` (or `--follow`) to follow HTTP redirects. | Property | Value | | -------- | -------------------------- | | Severity | Warning | | Category | Correctness | | Default | Enabled | | Auto-fix | Yes (`--fix --fix-unsafe`) | ## Description Flags `curl` commands in `RUN` instructions that are missing a redirect-following flag. Without such a flag, curl will not follow HTTP redirects (301, 302, 307, 308), which can cause downloads to silently fail when URLs are relocated. Other Dockerfile download mechanisms follow redirects by default: * **`ADD `** follows up to 10 redirects (Go `net/http` default behavior) * **`wget`** follows up to 20 redirects by default ### `--location` vs `--follow` The fix depends on how curl is invoked: * **`--location`** (`-L`) — suggested for standard downloads (GET, POST, PUT, or no `-X`). This is the classic redirect flag available in all curl versions. * **`--follow`** — suggested when `-X`/`--request` specifies a method other than GET, POST, or PUT (e.g., DELETE, PATCH, QUERY). `--location` changes non-GET methods to GET on 301/302 redirects, which breaks these methods. `--follow` (curl 8.16.0+) preserves the HTTP method across redirects. ## Examples ### Before (violation) ```dockerfile theme={null} FROM ubuntu:22.04 RUN curl -fsSo /tmp/file.tar.gz https://example.com/file.tar.gz RUN curl -X DELETE https://example.com/api/item/123 ``` ### After (fixed with --fix --fix-unsafe) ```dockerfile theme={null} FROM ubuntu:22.04 RUN curl --location -fsSo /tmp/file.tar.gz https://example.com/file.tar.gz RUN curl --follow -X DELETE https://example.com/api/item/123 ``` ## Exceptions The rule does **not** trigger when: * `-L` or `--location` is already present (including combined flags like `-fsSL`) * `--location-trusted` is present (implies redirect following) * `--follow` is already present (curl 8.16.0+) * All URL arguments point to IP addresses (e.g., `http://127.0.0.1:8080/health`, `http://10.0.0.1/api`), since local/internal services typically don't redirect * The curl command is a non-transfer invocation (`--help`, `--version`, `--manual`) where redirect flags have no effect ## Limitations * Only detects `curl` commands directly visible to the shell parser; commands inside variables or dynamically constructed strings are not analyzed * Skips non-POSIX shells (e.g., PowerShell stages) ## References * [curl `--location` documentation](https://curl.se/docs/manpage.html#-L) * [Follow redirects, but differently](https://daniel.haxx.se/blog/2025/08/06/follow-redirects-but-differently/) — curl 8.16.0 `--follow` flag * [Dockerfile `ADD` reference](https://docs.docker.com/reference/dockerfile/#add) # tally/eol-last Source: https://tally.wharflab.com/rules/tally/eol-last Enforces a newline at the end of non-empty files. Enforces a newline at the end of non-empty files. | Property | Value | | -------- | ---------- | | Severity | Style | | Category | Style | | Default | Enabled | | Auto-fix | Yes (safe) | ## Description POSIX defines a line as a sequence of characters ending with a newline. Files that lack a trailing newline can cause issues with file concatenation, produce messy diffs in version control, and interfere with shell prompts when printed with `cat`. This rule enforces (or prohibits) a final newline in Dockerfiles, mirroring ESLint's [`eol-last`](https://eslint.style/rules/eol-last) rule. In the default `"always"` mode, the rule reports a violation when a non-empty file does not end with `\n`. In `"never"` mode, it reports when a file does end with `\n`. Empty files (zero bytes) are always ignored. ## Companion editor configuration Use [EditorConfig integration](/integrations/editorconfig) to configure IDEs and editors to insert the final newline before tally reports it. ## Examples ### Bad (mode: "always") ```dockerfile theme={null} FROM alpine:3.20 RUN apk --no-cache add ca-certificates ENTRYPOINT ["/app"]⌁ ``` (where `⌁` marks the end of file with no trailing newline) ### Good (mode: "always") ```dockerfile theme={null} FROM alpine:3.20 RUN apk --no-cache add ca-certificates ENTRYPOINT ["/app"] ``` ## Configuration Default (no config needed): ```toml theme={null} # Enabled by default with mode = "always" ``` Disable the rule: ```toml theme={null} [rules.tally.eol-last] severity = "off" ``` Require files to **not** end with a newline: ```toml theme={null} [rules.tally.eol-last] mode = "never" ``` ## Options | Option | Type | Default | Description | | ------ | ------ | ---------- | ----------------------------------------------------------- | | `mode` | string | `"always"` | `"always"` requires a final newline; `"never"` prohibits it | ## Auto-fix This rule provides a safe auto-fix: ```bash theme={null} tally lint --fix Dockerfile ``` * In `"always"` mode, a missing final newline is appended. * In `"never"` mode, the trailing newline is removed. ## Related Rules * [`tally/no-multiple-empty-lines`](./no-multiple-empty-lines) — controls excess blank lines at the end (and beginning) of files * [`tally/no-trailing-spaces`](./no-trailing-spaces) — removes trailing whitespace on individual lines # tally/epilogue-order Source: https://tally.wharflab.com/rules/tally/epilogue-order Runtime-configuration instructions should appear at the end of each output stage in canonical order. Runtime-configuration instructions should appear at the end of each output stage in canonical order. | Property | Value | | -------- | ---------- | | Severity | Style | | Category | Style | | Default | Enabled | | Auto-fix | Yes (safe) | ## Description Dockerfiles should end each output stage with runtime-configuration instructions in a canonical order: **STOPSIGNAL, HEALTHCHECK, ENTRYPOINT, CMD**. These epilogue instructions configure how the container runs rather than how the image is built, and placing them at the end of the stage makes the Dockerfile easier to read and maintain. This rule checks two conditions for each applicable stage: 1. **Position**: All epilogue instructions must appear at the end of the stage (no build instructions like RUN, COPY, ENV after them) 2. **Order**: Among the epilogue instructions, they must appear in canonical order **Applicable stages**: The final stage and any stage with no dependents (not referenced by `COPY --from`, `FROM`, or `RUN --mount=from`). Intermediate builder stages are skipped since they typically don't use epilogue instructions. ## Examples ### Bad ```dockerfile theme={null} FROM alpine:3.20 CMD ["/app", "serve"] RUN apk add --no-cache ca-certificates ENTRYPOINT ["/app"] ``` CMD appears before RUN (position violation), and CMD comes before ENTRYPOINT (order violation). ### Good ```dockerfile theme={null} FROM alpine:3.20 RUN apk add --no-cache ca-certificates ENTRYPOINT ["/app"] CMD ["serve"] ``` All build instructions come first, then epilogue instructions in canonical order. ### Multi-stage (builder skipped) ```dockerfile theme={null} FROM golang:1.21 AS builder RUN go build -o /app # No violation here - builder stage is skipped FROM alpine:3.20 COPY --from=builder /app /app ENTRYPOINT ["/app"] CMD ["serve"] ``` ## Auto-fix This rule provides a safe auto-fix that moves epilogue instructions to the end of the stage in canonical order: ```bash theme={null} tally lint --fix Dockerfile ``` The fix: * Removes each epilogue instruction from its current position * Inserts all epilogue instructions at the end of the stage in canonical order * Preserves preceding comments and continuation lines When duplicate epilogue instructions of the same type exist (e.g., two CMD instructions), the fix is skipped for safety. The `MultipleInstructionsDisallowed` rule handles duplicate removal. ## Cross-rule interactions | Rule | Interaction | | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `tally/newline-between-instructions` | Runs after this rule's fix (priority 200 vs 175). Normalizes blank lines between the reordered epilogue instructions. The combined result is stable. | | `buildkit/MultipleInstructionsDisallowed` | Runs before (sync fix). Removes duplicate CMD/ENTRYPOINT/HEALTHCHECK. If duplicates remain (rule disabled), this rule skips the fix for safety. | # tally/gpu/cuda-version-mismatch Source: https://tally.wharflab.com/rules/tally/gpu/cuda-version-mismatch CUDA-specific pip/conda wheel version does not match the base image's CUDA toolkit. CUDA-specific pip/conda wheel version does not match the base image's CUDA toolkit. | Property | Value | | -------- | ---------------- | | Severity | Warning | | Category | Correctness | | Default | Enabled | | Auto-fix | Yes (suggestion) | ## Description Detects `RUN` instructions where pip, uv, or conda installs reference a CUDA version that does not match the base image's CUDA toolkit version. A mismatch can cause: * Silent fallback to CPU execution * Runtime CUDA errors or build failures * Subtle performance degradation The rule supports four detection paths: 1. **pip/pip3 package suffixes** -- `torch==2.0.0+cu118` 2. **pip/pip3/uv index URLs** -- `--index-url https://download.pytorch.org/whl/cu118` 3. **uv `--torch-backend`** -- `--torch-backend cu118` 4. **conda/mamba/micromamba** -- `pytorch-cuda=11.8` or `cudatoolkit=11.8` ## Why this matters * **Silent CPU fallback** -- PyTorch may load but silently use CPU instead of GPU when the CUDA wheel version doesn't match the available CUDA runtime * **Runtime crashes** -- mismatched CUDA versions can produce cryptic `CUDA error` messages at runtime * **Copy-paste bugs** -- the most common real-world pattern is upgrading the base image CUDA version but forgetting to update the pip `--index-url` suffix ## Examples ### Violation ```dockerfile theme={null} # Base provides CUDA 12.1, but pip installs CUDA 11.8 wheels FROM nvidia/cuda:12.1.0-devel-ubuntu20.04 RUN pip install --index-url https://download.pytorch.org/whl/cu118 torch ``` ```dockerfile theme={null} # Base provides CUDA 12.4, but --extra-index-url uses CUDA 11.8 FROM nvidia/cuda:12.4.1-cudnn-devel-ubuntu22.04 RUN pip install --extra-index-url https://download.pytorch.org/whl/cu118 xformers ``` ```dockerfile theme={null} # Base provides CUDA 12.2, but conda installs CUDA 11.8 pytorch FROM nvidia/cuda:12.2.0-devel-ubuntu22.04 RUN conda install -y pytorch pytorch-cuda=11.8 -c pytorch -c nvidia ``` ```dockerfile theme={null} # Base provides CUDA 12.4, but uv uses CUDA 11.8 backend FROM nvidia/cuda:12.4.0-runtime-ubuntu22.04 RUN uv pip install --torch-backend cu118 torch ``` ### No violation ```dockerfile theme={null} # Exact match: CUDA 11.8 base with cu118 wheels FROM nvidia/cuda:11.8.0-base-ubuntu22.04 RUN pip install --index-url https://download.pytorch.org/whl/cu118 torch ``` ```dockerfile theme={null} # Forward-compatible: cu121 wheel on CUDA 12.6 base (older minor is fine) FROM nvidia/cuda:12.6.0-runtime-ubuntu22.04 RUN pip install --index-url https://download.pytorch.org/whl/cu121 torch ``` ```dockerfile theme={null} # Non-CUDA base: rule does not apply FROM ubuntu:22.04 RUN pip install --index-url https://download.pytorch.org/whl/cu121 torch ``` ## Version compatibility The rule uses NVIDIA's forward compatibility guarantee: * **Same major, wheel minor at or below base minor** -- OK (forward-compatible) * **Same major, wheel minor above base minor** -- Mismatch (wheel needs newer CUDA) * **Different major** -- Always a mismatch ### CUDA suffix mapping | Suffix | CUDA version | | ------- | ------------ | | `cu118` | 11.8 | | `cu121` | 12.1 | | `cu124` | 12.4 | | `cu126` | 12.6 | | `cu128` | 12.8 | ## Fix suggestions The rule offers two fix alternatives: 1. **Update the wheel/index to match the base image** -- preferred when the base image has a higher CUDA version (the common case: base was upgraded but pip URL was not) 2. **Update the base image to match the wheel** -- preferred when the wheel targets a newer CUDA version than the base Both fixes use `FixSuggestion` safety -- verify the target wheel or image tag exists before applying. ## Applicability This rule fires when: * The base image is `nvidia/cuda:*` (or `docker.io/nvidia/cuda:*`) * The CUDA version can be parsed from the image tag * A CUDA version reference is found in a `RUN` instruction * In multi-stage builds, stages that inherit from a CUDA-based parent stage (`FROM builder` where `builder` uses `nvidia/cuda:*`) also trigger the rule. In this case, only the "update wheel/index" fix is offered -- the "update base image" fix is skipped since the `FROM` line references a stage name, not an image tag. It does **not** fire on: * Non-NVIDIA base images * Digest-only or ARG-based image tags (version cannot be determined) * pip installs without CUDA suffixes or index URLs ## Configuration This rule has no rule-specific options. ```toml theme={null} [rules.tally.gpu.cuda-version-mismatch] severity = "warning" ``` ## References * [PyTorch Previous Versions](https://pytorch.org/get-started/previous-versions/) * [NVIDIA CUDA Docker Hub](https://hub.docker.com/r/nvidia/cuda/) * [uv PyTorch integration](https://docs.astral.sh/uv/guides/integration/pytorch/) * [NVIDIA CUDA Compatibility](https://docs.nvidia.com/deploy/cuda-compatibility/) # tally/gpu/no-buildtime-gpu-queries Source: https://tally.wharflab.com/rules/tally/gpu/no-buildtime-gpu-queries GPU hardware is not available during `docker build`; runtime GPU queries in `RUN` will fail or return misleading results. GPU hardware is not available during `docker build`; runtime GPU queries in `RUN` will fail or return misleading results. | Property | Value | | -------- | ----------- | | Severity | Error | | Category | Correctness | | Default | Enabled | | Auto-fix | No | ## Description Detects `RUN` instructions that query GPU hardware at build time. GPU devices are not attached during a normal `docker build`, so commands like `nvidia-smi` and runtime framework checks like `torch.cuda.is_available()` will either fail outright or return misleading results (e.g., zero devices, `False`). This rule does **not** fire on `CMD` or `ENTRYPOINT`, where GPU queries are expected to run at container startup. ## Why this matters * **Build failures** -- `nvidia-smi` will exit non-zero when no GPU is present, breaking the build * **Silent wrong results** -- `torch.cuda.is_available()` returns `False` at build time, which can cause downstream logic to skip GPU code paths or produce incorrect configuration * **Misleading smoke tests** -- a passing build does not mean GPU support works; the check must happen at runtime * **Official guidance** -- Hugging Face explicitly warns that GPU hardware is not available during `docker build` ## Detected patterns ### GPU query commands | Command | Description | | --------------------- | --------------------------------------------------------- | | `nvidia-smi` | NVIDIA System Management Interface (queries GPU hardware) | | `nvidia-debugdump` | NVIDIA debug information dump | | `nvidia-persistenced` | NVIDIA persistence daemon (requires GPU) | ### Python/ML framework runtime checks | Pattern | Description | | ----------------------------------- | ---------------------------------- | | `torch.cuda.is_available()` | PyTorch CUDA availability check | | `torch.cuda.device_count()` | PyTorch CUDA device count | | `torch.cuda.get_device_name()` | PyTorch CUDA device name query | | `torch.cuda.current_device()` | PyTorch current CUDA device | | `tf.test.is_gpu_available()` | TensorFlow GPU availability check | | `tf.config.list_physical_devices()` | TensorFlow physical device listing | ## Examples ### Violation ```dockerfile theme={null} FROM nvidia/cuda:12.2.0-devel-ubuntu22.04 RUN nvidia-smi ``` ```dockerfile theme={null} FROM nvidia/cuda:12.2.0-devel-ubuntu22.04 RUN python3 -c "import torch; print(torch.cuda.is_available())" ``` ```dockerfile theme={null} FROM tensorflow/tensorflow:2.14.0-gpu RUN python -c "import tensorflow as tf; print(tf.test.is_gpu_available())" ``` ### No violation ```dockerfile theme={null} # GPU query in CMD runs at container startup — correct FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 CMD ["nvidia-smi", "--loop=10"] ``` ```dockerfile theme={null} # GPU query in ENTRYPOINT runs at container startup — correct FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 ENTRYPOINT ["python3", "-c", "import torch; print(torch.cuda.is_available())"] ``` ```dockerfile theme={null} # Normal package installation — no GPU query FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 RUN apt-get update && apt-get install -y python3 python3-pip ``` ## Configuration This rule has no rule-specific options. ```toml theme={null} [rules.tally.gpu.no-buildtime-gpu-queries] severity = "error" ``` ## References * [Hugging Face Docker Spaces GPU docs](https://huggingface.co/docs/hub/main/en/spaces-sdks-docker) -- explicitly warns GPU is unavailable during build * [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/docker-specialized.html) # tally/gpu/no-container-runtime-in-image Source: https://tally.wharflab.com/rules/tally/gpu/no-container-runtime-in-image NVIDIA container runtime packages belong on the host, not inside the image. NVIDIA container runtime packages belong on the host, not inside the image. | Property | Value | | -------- | ----------- | | Severity | Warning | | Category | Correctness | | Default | Enabled | | Auto-fix | No | ## Description Detects `RUN` instructions that install NVIDIA Container Toolkit host-side packages (`nvidia-container-toolkit`, `nvidia-docker2`, `libnvidia-container*`) inside the container image via a package manager (`apt`, `apt-get`, `yum`, `dnf`, `microdnf`, `apk`). These packages are part of the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html), which hooks into the container runtime on the **host** to expose GPUs to containers. Installing them inside the image does not make the image GPU-enabled, wastes image layers, and can mask the real requirement: that the host or cluster runtime must have the toolkit configured. ## Why this matters * **Wrong layer** -- the toolkit runs on the host/node, not in the container * **Does not enable GPU access** -- GPU device injection is handled by the container runtime (e.g., `nvidia-container-runtime`, CDI), not by packages inside the image * **Bloats the image** -- the toolkit pulls in host-specific libraries that serve no purpose in the container filesystem * **Hides requirements** -- a working GPU setup depends on the host runtime configuration, not on image contents ## Examples ### Violation ```dockerfile theme={null} FROM ubuntu:22.04 RUN apt-get update && apt-get install -y nvidia-container-toolkit ``` ```dockerfile theme={null} FROM centos:7 RUN yum install -y nvidia-docker2 ``` ```dockerfile theme={null} FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 RUN apt-get update && apt-get install -y libnvidia-container1 ``` ### No violation ```dockerfile theme={null} # GPU base image with application packages only FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 RUN apt-get update && apt-get install -y python3 python3-pip ``` ```dockerfile theme={null} # Non-GPU image with unrelated packages FROM ubuntu:22.04 RUN apt-get update && apt-get install -y curl wget git ``` ## Matched packages | Package | Description | | -------------------------- | ------------------------------------------------------------------------------------------- | | `nvidia-container-toolkit` | Main toolkit meta-package (CLI, hook, CDI generator) | | `nvidia-docker2` | Legacy wrapper for Docker runtime integration | | `libnvidia-container*` | Low-level container GPU library (`libnvidia-container1`, `libnvidia-container-tools`, etc.) | ## Configuration This rule has no rule-specific options. ```toml theme={null} [rules.tally.gpu.no-container-runtime-in-image] severity = "warning" ``` ## References * [NVIDIA Container Toolkit Install Guide](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) * [NVIDIA Container Toolkit Architecture](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/arch-overview.html) # tally/gpu/no-hardcoded-visible-devices Source: https://tally.wharflab.com/rules/tally/gpu/no-hardcoded-visible-devices GPU visibility is deployment policy; hardcoding it in the image reduces portability. GPU visibility is deployment policy; hardcoding it in the image reduces portability. | Property | Value | | -------- | ----------- | | Severity | Warning | | Category | Correctness | | Default | Enabled | | Auto-fix | Partial | ## Description Detects `ENV` instructions that hardcode GPU device visibility variables (`NVIDIA_VISIBLE_DEVICES`, `CUDA_VISIBLE_DEVICES`) inside the image. GPU visibility is deployment policy that should be set at runtime via `docker run --gpus`, `NVIDIA_VISIBLE_DEVICES` in the orchestrator, or similar mechanisms \-- not baked into the container image. ## Why this matters * **Portability** -- images with hardcoded device indices or UUIDs cannot run on hosts with different GPU topologies without rebuilding * **Orchestrator conflict** -- Kubernetes device plugins, Slurm, and other schedulers set GPU visibility externally; image-level settings can conflict with or override orchestrator intent * **Redundancy** -- official `nvidia/cuda` base images already set `NVIDIA_VISIBLE_DEVICES=all` via image labels; re-declaring it in the Dockerfile is pure noise ## What is flagged | Pattern | Flagged? | Fix safety | | --------------------------------------------------------- | --------------------------------------- | --------------------------- | | `ENV NVIDIA_VISIBLE_DEVICES=all` on `nvidia/cuda:*` base | Yes (redundant) | `FixSafe` -- safe to delete | | `ENV NVIDIA_VISIBLE_DEVICES=0` or `=0,1` (device indices) | Yes | `FixSuggestion` | | `ENV NVIDIA_VISIBLE_DEVICES=GPU-` or `MIG-` | Yes | `FixSuggestion` | | `ENV CUDA_VISIBLE_DEVICES=` | Yes | `FixSuggestion` | | `ENV NVIDIA_VISIBLE_DEVICES=all` on non-CUDA base | No -- intentional for custom GPU images | -- | | `ENV NVIDIA_VISIBLE_DEVICES=none` / `void` / empty | No -- intentional disable signal | -- | | `ENV NVIDIA_VISIBLE_DEVICES=${VAR}` (variable reference) | No -- parameterized, not hardcoded | -- | | `ENV CUDA_VISIBLE_DEVICES=none` / `NoDevFiles` / empty | No -- intentional disable | -- | ## Examples ### Violation ```dockerfile theme={null} # Redundant: nvidia/cuda already sets NVIDIA_VISIBLE_DEVICES=all FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 ENV NVIDIA_VISIBLE_DEVICES=all ``` ```dockerfile theme={null} # Hardcoded device indices make the image non-portable FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 ENV NVIDIA_VISIBLE_DEVICES=0,1 ``` ```dockerfile theme={null} # CUDA_VISIBLE_DEVICES bakes deployment policy into the image FROM ubuntu:22.04 ENV CUDA_VISIBLE_DEVICES=0 ``` ```dockerfile theme={null} # GPU UUIDs are host-specific FROM ubuntu:22.04 ENV NVIDIA_VISIBLE_DEVICES=GPU-aaaa-bbbb-cccc-dddd-eeee-ffffffffffff ``` ### No violation ```dockerfile theme={null} # NVIDIA_VISIBLE_DEVICES=all on a non-CUDA base is intentional FROM ubuntu:22.04 ENV NVIDIA_VISIBLE_DEVICES=all ``` ```dockerfile theme={null} # Disable signals are intentional FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 ENV NVIDIA_VISIBLE_DEVICES=none ``` ```dockerfile theme={null} # Variable references are parameterized, not hardcoded FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 ARG GPU_DEVICES=all ENV NVIDIA_VISIBLE_DEVICES=${GPU_DEVICES} ``` ## Auto-fix behavior The rule offers two fix safety levels: * **`FixSafe`** (applied with `--fix`): removes the redundant `NVIDIA_VISIBLE_DEVICES=all` on `nvidia/cuda` base images. This is 100% behavior-preserving because the base image already sets this value. * **`FixSuggestion`** (applied with `--fix --fix-unsafe`): removes hardcoded device indices, UUIDs, or `CUDA_VISIBLE_DEVICES` values. This improves portability but changes deployment semantics -- the user must ensure GPU visibility is provided at runtime. For multi-key `ENV` instructions, only the flagged key is removed; other keys are preserved. ## Configuration This rule has no rule-specific options. ```toml theme={null} [rules.tally.gpu.no-hardcoded-visible-devices] severity = "warning" ``` ## References * [NVIDIA Container Toolkit: Environment Variables](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/docker-specialized.html) * [CUDA Environment Variables](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#env-vars) # tally/gpu/no-redundant-cuda-install Source: https://tally.wharflab.com/rules/tally/gpu/no-redundant-cuda-install CUDA packages are already provided by the nvidia/cuda base image. CUDA packages are already provided by the nvidia/cuda base image. | Property | Value | | -------- | ----------- | | Severity | Warning | | Category | Correctness | | Default | Enabled | | Auto-fix | No | ## Description Detects `RUN` instructions that install CUDA userspace packages via a package manager (`apt`, `apt-get`, `yum`, `dnf`, `microdnf`, `apk`) in stages that already inherit from `nvidia/cuda:*`. The rule is **flavor-aware**: it parses the image tag to determine the variant (`base`, `runtime`, or `devel`) and only flags packages that the variant already includes. For example, installing `cuda-toolkit` on a `runtime` image is legitimate (runtime does not include the toolkit), but installing `cuda-runtime` on a `runtime` image is redundant. ## Why this matters * **Redundant work** -- the base image already provides the CUDA stack for the selected variant * **Version drift** -- the package manager may install a different CUDA version than the one baked into the base image, causing subtle incompatibilities * **Image bloat** -- duplicate CUDA libraries waste space in the image layers * **Maintenance burden** -- two sources of truth for the CUDA version make upgrades harder ## Examples ### Violation ```dockerfile theme={null} # devel includes the full toolkit -- reinstalling is redundant FROM nvidia/cuda:12.2.0-devel-ubuntu22.04 RUN apt-get update && apt-get install -y cuda-toolkit ``` ```dockerfile theme={null} # cudnn tag already includes cuDNN -- reinstalling is redundant FROM nvidia/cuda:12.2.0-cudnn-devel-ubuntu22.04 RUN apt-get update && apt-get install -y libcudnn8 ``` ```dockerfile theme={null} # runtime includes cuda-runtime -- reinstalling is redundant FROM nvidia/cuda:12.2.0-runtime-centos7 RUN yum install -y cuda-runtime-12-2 ``` ### No violation ```dockerfile theme={null} # runtime does NOT include the toolkit -- this install is legitimate FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 RUN apt-get update && apt-get install -y cuda-toolkit ``` ```dockerfile theme={null} # nvidia/cuda base with application packages only FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 RUN apt-get update && apt-get install -y python3 python3-pip ``` ```dockerfile theme={null} # Non-nvidia/cuda base -- intentional CUDA install is not flagged FROM ubuntu:22.04 RUN apt-get update && apt-get install -y nvidia-cuda-toolkit ``` ## Flavor-aware matching The rule maps packages to the nvidia/cuda image variant that includes them: | Package | Included in | Match type | | -------------------------------------------------- | -------------------- | ------------ | | `cuda`, `cuda-runtime` | base, runtime, devel | Exact | | `cuda-runtime-*`, `cuda-compat-*` | base, runtime, devel | Prefix | | `cuda-libraries`, `cuda-libraries-*` | runtime, devel | Exact/Prefix | | `nvidia-cuda-toolkit`, `cuda-toolkit`, `cuda-nvcc` | devel | Exact | | `cuda-toolkit-*`, `cuda-nvcc-*` | devel | Prefix | | `libcudnn*` | cudnn tags only | Prefix | TensorRT packages (`tensorrt*`) are never flagged because standard `nvidia/cuda` tags do not include TensorRT. When the tag cannot be parsed (e.g., digest-only or ARG-based), the rule defaults to `devel` to avoid false positives. ## Applicability This rule only fires on stages where the base image is `nvidia/cuda:*` (or `docker.io/nvidia/cuda:*`). It does **not** fire on: * Stages with a non-NVIDIA base image (e.g., `ubuntu:22.04`) * Stages using other NVIDIA images (e.g., `nvcr.io/nvidia/pytorch:*`, `nvidia/cudagl:*`) * Stages that reference another build stage (`FROM builder`) ## Configuration This rule has no rule-specific options. ```toml theme={null} [rules.tally.gpu.no-redundant-cuda-install] severity = "warning" ``` ## References * [NVIDIA CUDA Docker Hub](https://hub.docker.com/r/nvidia/cuda/) * [NVIDIA CUDA image variants](https://gitlab.com/nvidia/container-images/cuda/blob/master/doc/supported-tags.md) # tally/gpu/prefer-minimal-driver-capabilities Source: https://tally.wharflab.com/rules/tally/gpu/prefer-minimal-driver-capabilities `NVIDIA_DRIVER_CAPABILITIES=all` exposes more driver surface than most workloads need; prefer a minimal capability set. `NVIDIA_DRIVER_CAPABILITIES=all` exposes more driver surface than most workloads need; prefer a minimal capability set. | Property | Value | | -------- | --------------- | | Severity | Info | | Category | Correctness | | Default | Enabled | | Auto-fix | Suggestion only | ## Description Detects `ENV NVIDIA_DRIVER_CAPABILITIES=all` in Dockerfiles. The `all` capability set mounts every NVIDIA driver library and binary into the container, but most ML and CUDA workloads only need `compute,utility` (NVIDIA's documented default). A smaller set follows the principle of least privilege and avoids potential compatibility issues. ## Why this matters * **Least privilege** -- `all` exposes driver capabilities (`graphics`, `video`, `display`, `compat32`) that most inference and training workloads never use * **Compatibility** -- mounting unnecessary driver components can surface driver/library version conflicts in environments where the host driver differs from what the image expects * **Clarity** -- explicitly listing needed capabilities documents the workload's actual requirements ## What is flagged | Pattern | Flagged? | Fix safety | | ------------------------------------------------------------ | --------------------- | --------------- | | `ENV NVIDIA_DRIVER_CAPABILITIES=all` | Yes | `FixSuggestion` | | `ENV NVIDIA_DRIVER_CAPABILITIES=ALL` (case-insensitive) | Yes | `FixSuggestion` | | `ENV NVIDIA_DRIVER_CAPABILITIES=compute,utility` | No -- already minimal | -- | | `ENV NVIDIA_DRIVER_CAPABILITIES=graphics,compute,utility` | No -- intentional | -- | | `ENV NVIDIA_DRIVER_CAPABILITIES=` (empty) | No | -- | | `ENV NVIDIA_DRIVER_CAPABILITIES=${VAR}` (variable reference) | No -- parameterized | -- | ## Examples ### Violation ```dockerfile theme={null} # Exposes all driver capabilities unnecessarily FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 ENV NVIDIA_DRIVER_CAPABILITIES=all ``` ```dockerfile theme={null} # Same issue on a custom GPU base image FROM ubuntu:22.04 ENV NVIDIA_DRIVER_CAPABILITIES=all ``` ### No violation ```dockerfile theme={null} # Explicit minimal set -- preferred FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 ENV NVIDIA_DRIVER_CAPABILITIES=compute,utility ``` ```dockerfile theme={null} # Workload that genuinely needs graphics FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 ENV NVIDIA_DRIVER_CAPABILITIES=graphics,compute,utility ``` ```dockerfile theme={null} # Parameterized -- not hardcoded FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 ARG CAPS=all ENV NVIDIA_DRIVER_CAPABILITIES=${CAPS} ``` ## Auto-fix behavior The rule offers a **`FixSuggestion`** (applied with `--fix --fix-unsafe`): replaces `all` with `compute,utility`. This is safe for most ML/CUDA workloads but may break workloads that genuinely need `graphics`, `video`, or `display` capabilities -- review before accepting. For multi-key `ENV` instructions, only the `NVIDIA_DRIVER_CAPABILITIES` value is replaced; other keys are preserved. ## Configuration This rule has no rule-specific options. ```toml theme={null} [rules.tally.gpu.prefer-minimal-driver-capabilities] severity = "info" ``` ## References * [NVIDIA Container Toolkit: Environment Variables](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/docker-specialized.html) # tally/gpu/prefer-runtime-final-stage Source: https://tally.wharflab.com/rules/tally/gpu/prefer-runtime-final-stage Final stage uses an NVIDIA devel image without clear build-time needs; prefer a runtime or base variant for the shipped stage. Final stage uses an NVIDIA devel image without clear build-time needs; prefer a runtime or base variant for the shipped stage. | Property | Value | | -------- | -------------- | | Severity | Warning | | Category | Best Practices | | Default | Enabled | | Auto-fix | Not available | ## Description Detects when the final stage of a Dockerfile uses `nvidia/cuda:*devel*` as its base image without obvious compile-time needs such as `nvcc`, `gcc`, `make`, or `cmake`. The `devel` variant includes the full CUDA compiler toolchain, development headers, and static libraries, which can add several gigabytes to the final image. ## Why this matters * **Image size** -- `devel` images are typically 2--4 GB larger than the corresponding `runtime` variant due to `nvcc`, development headers, and static libraries * **Attack surface** -- shipping compiler toolchains and development headers in production images exposes unnecessary binaries that could be leveraged in a container escape or supply-chain attack * **Build cache efficiency** -- larger images take longer to pull, push, and layer-cache, slowing down CI/CD pipelines * **Best practice alignment** -- NVIDIA, Hugging Face, and major ML projects recommend using `devel` only in builder stages and switching to `runtime` or `base` for the shipped image ## What is flagged | Pattern | Flagged? | | ---------------------------------------------------------------------------- | ---------------------------------------------- | | Final stage `FROM nvidia/cuda:12.x-devel-*` with no compile signal | Yes | | Final stage `FROM nvidia/cuda:12.x-cudnn-devel-*` with no compile signal | Yes | | Final stage `FROM nvidia/cuda:12.x-devel-*` with `nvcc`, `gcc`, `make`, etc. | No -- legitimate build stage | | Final stage `FROM nvidia/cuda:12.x-devel-*` with `build-essential` installed | No -- build tools present | | Final stage `FROM nvidia/cuda:12.x-runtime-*` | No -- already a runtime variant | | Final stage `FROM nvidia/cuda:12.x-base-*` | No -- already a minimal variant | | Non-final stage using `devel` | No -- builder stages legitimately need `devel` | ## Examples ### Violation ```dockerfile theme={null} # Single-stage devel with no compilation FROM nvidia/cuda:12.2.0-devel-ubuntu22.04 RUN pip install torch CMD ["python", "app.py"] ``` ```dockerfile theme={null} # Multi-stage: final stage uses devel but only runs pre-built binaries FROM nvidia/cuda:12.2.0-devel-ubuntu22.04 AS builder RUN nvcc -o /app main.cu FROM nvidia/cuda:12.2.0-devel-ubuntu22.04 COPY --from=builder /app /app CMD ["/app"] ``` ### No violation ```dockerfile theme={null} # Final stage uses runtime variant FROM nvidia/cuda:12.2.0-devel-ubuntu22.04 AS builder RUN nvcc -o /app main.cu FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 COPY --from=builder /app /app CMD ["/app"] ``` ```dockerfile theme={null} # Final stage uses devel but has compile signal FROM nvidia/cuda:12.2.0-devel-ubuntu22.04 RUN nvcc -o /app main.cu CMD ["/app"] ``` ```dockerfile theme={null} # Devel only in builder stage FROM nvidia/cuda:12.2.0-devel-ubuntu22.04 AS builder RUN cmake . && make FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 COPY --from=builder /app /app CMD ["/app"] ``` ## Detection details The rule fires only when all of the following are true: 1. The final stage base image is `nvidia/cuda:*devel*` 2. No compile signal is detected in the final stage Compile signals that suppress the rule: * **Commands**: `nvcc`, `gcc`, `g++`, `make`, `cmake`, `ninja` * **Packages**: `build-essential`, `gcc`, `g++`, `make`, `cmake`, `ninja-build` When a `COPY --from=...` instruction is present in the final stage, the violation detail notes this as additional evidence that the stage serves as a runtime image. ## Configuration This rule has no rule-specific options. ```toml theme={null} [rules.tally.gpu.prefer-runtime-final-stage] severity = "warning" ``` ## References * [NVIDIA CUDA Docker images](https://hub.docker.com/r/nvidia/cuda/) * [Hugging Face Docker Spaces GPU docs](https://huggingface.co/docs/hub/main/en/spaces-sdks-docker) # tally/gpu/prefer-uv-over-conda Source: https://tally.wharflab.com/rules/tally/gpu/prefer-uv-over-conda Narrow GPU Python Dockerfiles can often be migrated from conda to uv for faster, lock-friendly installs. Narrow GPU Python Dockerfiles can often be migrated from conda to uv for faster, lock-friendly installs. | Property | Value | | -------- | ------------------------ | | Severity | Info | | Category | Best-practices | | Default | Enabled (experimental) | | Auto-fix | Yes (AI AutoFix, unsafe) | ## Description This rule fires on Dockerfiles that: * Use a **GPU/PyTorch-oriented base image** (`nvidia/cuda:*`, `nvcr.io/nvidia/*`, `pytorch/pytorch:*cuda*`, or a stage that inherits CUDA from one of these). * Install Python/ML packages (e.g. `torch`, `torchvision`, `transformers`, `flash-attn`, `xformers`) via `conda`, `mamba`, or `micromamba`. * Do **not** rely on a heavy conda environment-management workflow (no `conda env create`, no `environment.yml` / `conda-lock.yml` copied into the image). For that narrow, migratable slice the rule suggests an AI-assisted conversion to [uv](https://docs.astral.sh/uv/), which offers faster resolution, explicit CUDA wheel index support, and lock-friendly reproducibility. ## Why this matters * Conda resolution on GPU images is slow and can pull in unused Anaconda channels. * Many GPU images use conda only as a Python package installer — uv is a closer fit with `pip install uv && uv pip install --index-url https://download.pytorch.org/whl/cuXYZ ...`. * uv supports explicit CUDA wheel indexes, which makes the CUDA alignment story (see also [`tally/gpu/cuda-version-mismatch`](/rules/tally/gpu/cuda-version-mismatch)) simpler and more auditable. ## Examples ### Violation ```dockerfile theme={null} FROM nvidia/cuda:12.1.0-devel-ubuntu22.04 RUN conda install -y pytorch pytorch-cuda=12.1 -c pytorch -c nvidia CMD ["python"] ``` ```dockerfile theme={null} FROM pytorch/pytorch:2.1.0-cuda12.1-cudnn8-devel RUN mamba install -y numpy transformers ``` ```dockerfile theme={null} FROM nvidia/cuda:12.4.0-devel-ubuntu22.04 RUN micromamba install -y flash-attn xformers ``` ### No violation ```dockerfile theme={null} # Already uses uv. FROM nvidia/cuda:12.1.0-runtime-ubuntu22.04 RUN pip install uv && uv pip install --system --index-url https://download.pytorch.org/whl/cu121 torch ``` ```dockerfile theme={null} # Heavy conda environment workflow; migration is out of scope. FROM nvidia/cuda:12.1.0-devel-ubuntu22.04 COPY environment.yml /app/ RUN conda env create -f /app/environment.yml ``` ```dockerfile theme={null} # CPU base image; rule does not fire. FROM ubuntu:22.04 RUN conda install -y numpy ``` ```dockerfile theme={null} # conda installs only system packages; not a Python package workflow. FROM nvidia/cuda:12.1.0-devel-ubuntu22.04 RUN conda install -y gcc cmake ``` ## Auto-fix When the rule fires, it attaches an **unsafe**, async `SuggestedFix` backed by the AI AutoFix resolver. The AI agent is asked to: * Replace conda/mamba/micromamba Python/ML installs with `uv pip install ...`. * Install uv before its first use (via `pip install uv` or the official installer). * Preserve the base image and OS package installs. * Preserve runtime invariants in the final stage (`CMD`, `ENTRYPOINT`, `USER`, `WORKDIR`, `ENV`, `LABEL`, `EXPOSE`, `HEALTHCHECK`). * Output `NO_CHANGE` if the file is better left alone (e.g. an `environment.yml`-driven workflow snuck past detection). Applying the fix requires: * `--fix --fix-unsafe` * A configured ACP-capable agent in the config file (see the top-level `[ai]` section) The resolver returns a single edit that replaces the entire Dockerfile content. ## Applicability The rule fires when **all** of the following hold for one stage: 1. The stage is GPU-oriented: * base image resolves to `nvidia/cuda:*` (or `nvcr.io/nvidia/*`, `nvidia/cudagl:*`), or * base image resolves to `pytorch/pytorch:*` (or `nvcr.io/nvidia/pytorch:*`), or * `StageFacts.CUDAMajor > 0` (inherited via a stage reference). 2. A `RUN` invokes `conda`, `mamba`, or `micromamba` with an `install` subcommand that lists at least one known Python/ML package. 3. The Dockerfile as a whole does not show signs of a heavier conda workflow: * no `conda env create` / `mamba env create` / `micromamba env create` in any `RUN` * no `environment.yml`, `environment.yaml`, `conda-lock.yml`, or `conda-lock.yaml` in the build context Violations are emitted at most once per stage; the AI AutoFix rewrite is file-scoped. ## Configuration This rule has no rule-specific options. ```toml theme={null} [rules.tally.gpu.prefer-uv-over-conda] severity = "info" fix = "explicit" ``` ## References * [uv: PyTorch integration](https://docs.astral.sh/uv/guides/integration/pytorch/) * [uv: Docker integration](https://docs.astral.sh/uv/guides/integration/docker/) * [NVIDIA CUDA image tags](https://hub.docker.com/r/nvidia/cuda/) * [tally/gpu/cuda-version-mismatch](/rules/tally/gpu/cuda-version-mismatch) # tally/invalid-json-form Source: https://tally.wharflab.com/rules/tally/invalid-json-form Arguments appear to use JSON exec-form but contain invalid JSON. Arguments appear to use JSON exec-form but contain invalid JSON. | Property | Value | | -------- | ----------------------------------------- | | Severity | Error | | Category | Correctness | | Default | Enabled | | Auto-fix | Yes (suggestion, requires `--fix-unsafe`) | ## Description Several Dockerfile instructions (`CMD`, `ENTRYPOINT`, `RUN`, `SHELL`, `COPY`, `ADD`, `VOLUME`, `HEALTHCHECK CMD`, `ONBUILD `) accept JSON exec-form syntax: ```dockerfile theme={null} CMD ["executable", "param1", "param2"] ``` When the arguments start with `[` but contain invalid JSON (unquoted strings, single quotes, trailing commas), BuildKit's parser silently treats them as shell form. This produces unexpected behavior: * `CMD [bash, -lc, "echo hi"]` is treated as the shell command `[bash, -lc, "echo hi"]` rather than exec-form `["bash", "-lc", "echo hi"]`. * `SHELL [/bin/bash, -c]` causes a build error because `SHELL` requires valid JSON. The auto-fix rewrites the arguments as valid JSON. It is classified as `suggestion` because intent cannot be guaranteed -- review it before applying. ## Related Rules * [`buildkit/JSONArgsRecommended`](../buildkit/JSONArgsRecommended) -- recommends JSON exec-form for `CMD` and `ENTRYPOINT`. Because BuildKit falls back to shell-form when JSON is invalid, `JSONArgsRecommended` (info severity) also fires on the same instruction. tally's supersession processor automatically suppresses the lower-severity `JSONArgsRecommended` violation when this rule (error severity) is present at the same line — so users see only the more actionable `invalid-json-form` error. ## References * [Dockerfile reference -- CMD](https://docs.docker.com/reference/dockerfile/#cmd) * [Dockerfile reference -- ENTRYPOINT](https://docs.docker.com/reference/dockerfile/#entrypoint) * [Dockerfile reference -- SHELL](https://docs.docker.com/reference/dockerfile/#shell) ## Examples ### Bad ```dockerfile theme={null} FROM alpine:3.20 # Unquoted strings -- treated as shell form CMD [bash, -lc, "echo hello"] # Single quotes -- not valid JSON ENTRYPOINT ['/usr/bin/app', '--serve'] # Trailing comma -- invalid JSON RUN ["echo", "hello",] ``` ### Good ```dockerfile theme={null} FROM alpine:3.20 CMD ["bash", "-lc", "echo hello"] ENTRYPOINT ["/usr/bin/app", "--serve"] RUN ["echo", "hello"] ``` ## Configuration ```toml theme={null} [rules.tally.invalid-json-form] severity = "error" # Options: "off", "error", "warning", "info", "style" ``` # tally/invalid-onbuild-trigger Source: https://tally.wharflab.com/rules/tally/invalid-onbuild-trigger ONBUILD trigger instruction is not a valid Dockerfile instruction. ONBUILD trigger instruction is not a valid Dockerfile instruction. | Property | Value | | -------- | ----------------------------------------- | | Severity | Error | | Category | Correctness | | Default | Enabled | | Auto-fix | Yes (suggestion, requires `--fix-unsafe`) | ## Description `ONBUILD` allows a base image to embed instructions that run automatically when the image is used as a base for another build. However, the trigger instruction must be a valid Dockerfile keyword. Typo trigger keywords (e.g. `ONBUILD COPPY . /app`) successfully parse at the AST level but fail at build time. The outer `ONBUILD` keyword is valid, so the top-level unknown-instruction check does not catch these. This rule checks the trigger keyword itself. When the unknown trigger closely resembles a valid instruction (Levenshtein distance ≤ 2), tally proposes a correction. The fix is classified as `suggestion` because it is based on edit-distance inference — review it before applying. Forbidden triggers (`FROM`, `ONBUILD`, `MAINTAINER`) are excluded from this rule; they are already caught by [`hadolint/DL3043`](../hadolint/DL3043). ## References * [Dockerfile reference — ONBUILD limitations](https://docs.docker.com/reference/dockerfile/#onbuild-limitations) * [`hadolint/DL3043`](../hadolint/DL3043) — forbidden instructions as ONBUILD triggers ## Examples ### Bad ```dockerfile theme={null} FROM alpine:3.19 # COPPY is not a valid instruction — should be COPY ONBUILD COPPY . /app # RUNN is not a valid instruction — should be RUN ONBUILD RUNN apk add --no-cache ca-certificates ``` ### Good ```dockerfile theme={null} FROM alpine:3.19 ONBUILD COPY . /app ONBUILD RUN apk add --no-cache ca-certificates ``` ## Configuration ```toml theme={null} [rules.tally.invalid-onbuild-trigger] severity = "error" # Options: "off", "error", "warning", "info", "style" ``` # tally/js/node-gyp-cache-mounts Source: https://tally.wharflab.com/rules/tally/js/node-gyp-cache-mounts Native Node addon installs should cache node-gyp header downloads with BuildKit cache mounts. Native Node addon installs should cache node-gyp header downloads with BuildKit cache mounts. | Property | Value | | -------- | ----------------------------------------- | | Severity | Info | | Category | Performance | | Default | Enabled | | Auto-fix | Yes (suggestion, requires `--fix-unsafe`) | ## Description Flags JavaScript package install or rebuild `RUN` instructions in stages that look likely to compile native Node addons, but do not cache node-gyp's header download directory. The rule looks for native build signals such as: * OS packages: `python3`, `make`, `gcc`, `g++`, `build-base`, or `build-essential` * native addon helpers: `node-gyp`, `node-pre-gyp`, or `prebuild-install` * rebuild commands: `npm rebuild`, `pnpm rebuild`, or `yarn rebuild` * observable `package.json` dependencies such as `sharp`, `canvas`, `bcrypt`, `sqlite3`, `better-sqlite3`, `node-rdkafka`, `grpc`, or `isolated-vm` * dev-only native dependencies, but only when the install command includes dev packages ## What The Fix Adds The suggested fix adds a cache mount for node-gyp's devdir: ```dockerfile theme={null} --mount=type=cache,target=/root/.cache/node-gyp,id=node-gyp,sharing=locked ``` When `tally/prefer-package-cache-mounts` is not enabled for the same run, the fix also adds the matching package-manager cache mount for `npm`, `pnpm`, or `yarn`. When the generic cache-mount rule is enabled, this rule leaves package-manager caches to that rule to avoid duplicate suggestions. For shell-form `RUN` instructions, the fix also inserts: ```dockerfile theme={null} NPM_CONFIG_DEVDIR="/root/.cache/node-gyp" ``` If the stage already sets `NPM_CONFIG_DEVDIR`, `npm_config_devdir`, or `npm_package_config_node_gyp_devdir`, the rule uses that path for the cache mount instead of adding another environment assignment. ## Examples ### Before ```dockerfile theme={null} FROM node:22 RUN apt-get update && apt-get install -y python3 make g++ RUN npm ci --omit=dev ``` ### After ```dockerfile theme={null} FROM node:22 RUN apt-get update && apt-get install -y python3 make g++ RUN --mount=type=cache,target=/root/.npm,id=npm \ --mount=type=cache,target=/root/.cache/node-gyp,id=node-gyp,sharing=locked \ --mount=type=tmpfs,target=/tmp \ NPM_CONFIG_DEVDIR="/root/.cache/node-gyp" npm ci --omit=dev ``` ### Existing devdir ```dockerfile theme={null} FROM node:22 ENV npm_package_config_node_gyp_devdir=/cache/node-gyp RUN --mount=type=cache,target=/cache/node-gyp pnpm install --frozen-lockfile ``` No violation is reported because the install already uses an explicit node-gyp devdir cache. ## Guardrails * Windows container stages are skipped because BuildKit `RUN --mount` is not supported there. * Stages with explicit native build caches such as `ccache` or prebuild artifact cache mounts are skipped. * The rule does not suggest tmpfs for `node_modules` or package `build/` directories. Compiled `.node` artifacts must remain in the image layer. ## References * [node-gyp README](https://github.com/nodejs/node-gyp#readme) * [Dockerfile `RUN --mount` reference](https://docs.docker.com/reference/dockerfile/#run---mount) * [Docker cache optimization: Use cache mounts](https://docs.docker.com/build/cache/optimize/#use-cache-mounts) # tally/labels/no-buildx-git-overlap Source: https://tally.wharflab.com/rules/tally/labels/no-buildx-git-overlap Dockerfile labels should not duplicate git provenance labels generated by Buildx. Dockerfile labels should not duplicate git provenance labels generated by Buildx. | Property | Value | | -------- | ---------------------------------------------------------------------------- | | Severity | Warning | | Category | Correctness | | Default | Enabled, models `BUILDX_GIT_LABELS=full` | | Auto-fix | Suggestion, when `org.opencontainers.image.revision` can be mapped precisely | ## Description Buildx can generate image labels from the current git checkout when `BUILDX_GIT_LABELS` is enabled. Manually maintaining the same keys in the Dockerfile can leave stale source, revision, or Dockerfile-path metadata on the image. `org.opencontainers.image.revision` means the source control revision of the packaged content. A Dockerfile cannot know that value reliably unless the build system injects it for the exact checkout being built. When Buildx is already configured to generate the revision label, the Dockerfile label is redundant at best and stale at worst. This rule checks labels that affect the exported image. Labels in throwaway builder stages are ignored unless the final image inherits from that stage. ## Buildx modes | Mode | Generated labels checked | | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | `off`, `none`, or `strconv.ParseBool` false values such as `false`, `0`, `f`, `F` | Disabled | | `strconv.ParseBool` true values such as `true`, `1`, `t`, `T` | `org.opencontainers.image.revision`, `com.docker.image.source.entrypoint` | | `full` | The `true` labels plus `org.opencontainers.image.source` | ## Examples ### Bad ```dockerfile theme={null} FROM alpine:3.20 LABEL org.opencontainers.image.revision="${VCS_REF}" LABEL org.opencontainers.image.source="https://github.com/example/app" \ com.docker.image.source.entrypoint="Dockerfile" ``` ### Good ```dockerfile theme={null} FROM alpine:3.20 LABEL org.opencontainers.image.title="app" \ org.opencontainers.image.description="Example application" ``` Let Buildx attach git-derived labels at build time: ```bash theme={null} BUILDX_GIT_LABELS=full docker buildx build . ``` ## Fixes For `LABEL org.opencontainers.image.revision=...`, Tally offers two suggestion-level fixes when the source pair can be mapped precisely: * comment out the instruction, preserving the original text for review * delete the instruction or pair When `org.opencontainers.image.revision` appears inside a grouped `LABEL`, Tally removes only that key/value pair and preserves the unrelated labels. The comment-out fix inserts a commented standalone `LABEL` before the grouped instruction, then removes the active pair from the group. Fixes are scoped to the exported image's stage chain. If a final stage shadows an inherited `org.opencontainers.image.revision`, the suggested fix also removes the inherited copy so applying the fix once does not reveal the same issue again. Builder-only stages outside the exported chain are still ignored. `org.opencontainers.image.source` and `com.docker.image.source.entrypoint` are reported but not auto-fixed by this rule; they may be intentional static project metadata in some build workflows. ## Configuration Tally does not infer builder configuration from its own process environment. By default, this rule models `BUILDX_GIT_LABELS=full` because the diagnostics are informational and help keep git-derived metadata owned by the build system. Configure the mode explicitly when the repository uses a narrower Buildx git label policy. Boolean string values are parsed like Docker/Buildx documents `BUILDX_GIT_LABELS` parsing: `true`, `1`, `t`, and `T` all select the narrower non-`full` generated-label set. ```toml theme={null} [rules.tally.labels.no-buildx-git-overlap] buildx-git-labels = "full" ``` Disable the rule when Dockerfile labels intentionally own these keys: ```toml theme={null} [rules.tally.labels.no-buildx-git-overlap] buildx-git-labels = "off" ``` ## References * [Docker Build variables: `BUILDX_GIT_LABELS`](https://docs.docker.com/build/building/variables/#buildx_git_labels) * [Docker Buildx 0.10.0 release notes](https://github.com/docker/buildx/releases/tag/v0.10.0) * [Docker object labels](https://docs.docker.com/engine/manage-resources/labels/) * [OCI image annotations](https://github.com/opencontainers/image-spec/blob/main/annotations.md) ## Related Rules * [`tally/labels/no-stale-base-digest`](/rules/tally/labels/no-stale-base-digest) * [`tally/labels/no-duplicate-keys`](/rules/tally/labels/no-duplicate-keys) * [`tally/labels/valid-key`](/rules/tally/labels/valid-key) # tally/labels/no-duplicate-keys Source: https://tally.wharflab.com/rules/tally/labels/no-duplicate-keys LABEL keys should be set at most once per build stage. LABEL keys should be set at most once per build stage. | Property | Value | | -------- | ----------- | | Severity | Warning | | Category | Correctness | | Default | Enabled | | Auto-fix | Yes | ## Description Docker image labels are a key/value map. If the same key is written more than once in one build stage, Docker keeps the last value and the earlier value becomes review noise. This rule reports duplicate keys within the same stage. It does not compare labels across stages, because intermediate stages often describe different build artifacts and only the selected final stage's labels are published in the resulting image. Dynamic keys such as `LABEL "$PREFIX.name"=value` are skipped by duplicate detection because their final key cannot be proven statically. ## Auto-fix For redundant standalone `LABEL` instructions, the preferred fix comments out the earlier instruction. A second fix option removes it. The fix targets earlier labels because Docker keeps the last value for a key. When the duplicate key appears inside a multi-pair `LABEL` instruction, Tally still reports the duplicate but does not rewrite the instruction unless the obsolete pair can be removed without dropping unrelated labels. ## Examples ### Bad ```dockerfile theme={null} FROM alpine:3.20 LABEL org.opencontainers.image.source="https://github.com/example/app" LABEL org.opencontainers.image.source="https://github.com/example/app-v2" ``` ### Good ```dockerfile theme={null} FROM alpine:3.20 LABEL org.opencontainers.image.title="app" \ org.opencontainers.image.source="https://github.com/example/app" ``` ## Configuration No custom configuration options. The rule is enabled by default with severity `warning`. ```toml theme={null} [rules.tally.labels.no-duplicate-keys] severity = "off" ``` ## Related Rules * [`tally/labels/no-buildx-git-overlap`](/rules/tally/labels/no-buildx-git-overlap) * [`tally/labels/no-stale-base-digest`](/rules/tally/labels/no-stale-base-digest) * [`tally/labels/valid-key`](/rules/tally/labels/valid-key) # tally/labels/no-stale-base-digest Source: https://tally.wharflab.com/rules/tally/labels/no-stale-base-digest OCI base digest labels must be backed by a digest-pinned base image. OCI base digest labels must be backed by a digest-pinned base image. | Property | Value | | -------- | ------------------------------------------------------------------------- | | Severity | Warning | | Category | Correctness | | Default | Enabled | | Auto-fix | Suggestion, for standalone `LABEL` instructions and grouped `LABEL` pairs | ## Description `org.opencontainers.image.base.digest` is the digest of the image this image is based on. It is not the digest of the image produced by the current Dockerfile. For a Dockerfile-owned label to be trustworthy, the exported image's stage chain must include a digest-pinned external base image, such as `FROM alpine:3.20@sha256:...`, and the label value must match that digest. If the exported image is built from `FROM alpine:3.20`, `FROM scratch`, or a stage chain whose external base is not pinned by digest, a checked-in `org.opencontainers.image.base.digest` label can only drift from the actual base image selected during the build. This rule checks labels that affect the exported image. Labels in throwaway builder stages are ignored unless the final image inherits from that stage. ## Examples ### Bad: Unpinned base ```dockerfile theme={null} FROM alpine:3.20 LABEL org.opencontainers.image.base.digest="sha256:1111111111111111111111111111111111111111111111111111111111111111" ``` The `FROM` line is tag-based, so the Dockerfile does not prove the base image digest. ### Bad: Mismatched digest ```dockerfile theme={null} FROM alpine:3.20@sha256:1111111111111111111111111111111111111111111111111111111111111111 LABEL org.opencontainers.image.base.digest="sha256:2222222222222222222222222222222222222222222222222222222222222222" ``` The `FROM` line is digest-pinned, but the label disagrees with it. ### Good: Matching digest ```dockerfile theme={null} FROM alpine:3.20@sha256:1111111111111111111111111111111111111111111111111111111111111111 LABEL org.opencontainers.image.base.digest="sha256:1111111111111111111111111111111111111111111111111111111111111111" ``` ### Good: Omitted without pinned base ```dockerfile theme={null} FROM alpine:3.20 LABEL org.opencontainers.image.title="app" ``` When the base image is not digest-pinned, omit `org.opencontainers.image.base.digest`. ## Multi-stage Builds When the final image inherits from another Dockerfile stage, Tally follows that `FROM ` chain to the first external base image: ```dockerfile theme={null} FROM alpine:3.20@sha256:1111111111111111111111111111111111111111111111111111111111111111 AS base FROM base LABEL org.opencontainers.image.base.digest="sha256:1111111111111111111111111111111111111111111111111111111111111111" ``` This is valid because the exported image still traces back to a digest-pinned external base. A label in a builder-only stage is ignored when the exported image does not inherit from that stage. ## Fixes For `LABEL org.opencontainers.image.base.digest=...`, Tally offers two suggestion-level fixes when the source pair can be mapped precisely: * comment out the instruction, preserving the original text for review * delete the instruction or pair When `org.opencontainers.image.base.digest` appears inside a grouped `LABEL`, Tally removes only that key/value pair and preserves the unrelated labels. The comment-out fix inserts a commented standalone `LABEL` before the grouped instruction, then removes the active pair from the group. Fixes are scoped to the exported image's stage chain. If a final stage shadows an inherited stale base digest label, the suggested fix also removes the inherited copy so applying the fix once does not reveal the same issue again. Builder-only stages outside the exported chain are still ignored. ## Related Rules * [`tally/labels/no-buildx-git-overlap`](/rules/tally/labels/no-buildx-git-overlap) * [`tally/labels/no-duplicate-keys`](/rules/tally/labels/no-duplicate-keys) * [`tally/labels/valid-key`](/rules/tally/labels/valid-key) # tally/labels/prefer-grouped Source: https://tally.wharflab.com/rules/tally/labels/prefer-grouped Combine adjacent LABEL instructions in the same stage into one multi-line LABEL block. Adjacent `LABEL` instructions in the same stage are easier to review when they are written as a single multi-line `LABEL` block. | Property | Value | | -------- | ------- | | Severity | Info | | Category | Style | | Default | Enabled | | Auto-fix | Yes | ## Description Modern Docker no longer needs separate `LABEL` instructions to keep image layers small, so scattered LABELs serve no build purpose. Grouping them into one multi-line block keeps related image metadata together, makes diffs easier to read, and reduces the chance that two unrelated edits land on different lines of the same logical metadata block. The rule reports when a stage has at least `min-labels` label key/value pairs spread across two or more adjacent `LABEL` instructions. Two `LABEL` instructions are considered adjacent only when no other Dockerfile instruction (such as `ARG`, `ENV`, or `RUN`) and no comment line appears between them. Comments are treated as deliberate section breaks and never crossed by the auto-fix. This rule does not reorder labels. Stable ordering is left to a separate rule. ## Auto-fix The fix replaces the first `LABEL` instruction in the run with one multi-line `LABEL` containing every pair from the run, and deletes the remaining `LABEL` instructions in the run. Pairs are emitted in source order so that Docker's "last value wins" rule still produces the same effective image label map. The fix is suppressed when: * a key in the run is dynamic (for example `LABEL "$PREFIX.name"=...`) * a pair uses the legacy `LABEL key value` form * the run contains a duplicate key (let [`tally/labels/no-duplicate-keys`](/rules/tally/labels/no-duplicate-keys) rewrite the duplicate first) The fix output uses the same shape that [`tally/newline-per-chained-call`](/rules/tally/newline-per-chained-call) produces for split LABELs, so the two rules agree on a single canonical form and a re-lint after fixing is clean. ## Examples ### Bad ```dockerfile theme={null} FROM alpine:3.20 LABEL org.opencontainers.image.title="demo" LABEL org.opencontainers.image.description="example image" LABEL org.opencontainers.image.source="https://github.com/example/demo" LABEL org.opencontainers.image.licenses="Apache-2.0" ``` ### Good ```dockerfile theme={null} FROM alpine:3.20 LABEL org.opencontainers.image.title="demo" \ org.opencontainers.image.description="example image" \ org.opencontainers.image.source="https://github.com/example/demo" \ org.opencontainers.image.licenses="Apache-2.0" ``` ## Configuration ```toml theme={null} [rules.tally.labels.prefer-grouped] severity = "info" min-labels = 3 ``` | Option | Type | Default | Description | | ------------ | -------------- | ------- | ----------------------------------------------------------------------------------- | | `min-labels` | integer (>= 2) | `3` | Minimum total label key/value pairs across an adjacent run before the rule reports. | ## Related Rules * [`tally/labels/no-duplicate-keys`](/rules/tally/labels/no-duplicate-keys) — runs first when a duplicate key would otherwise be merged. * [`tally/newline-per-chained-call`](/rules/tally/newline-per-chained-call) — splits multi-pair `LABEL` instructions onto separate continuation lines; together with this rule it produces a canonical multi-line `LABEL` shape. * [`tally/labels/no-buildx-git-overlap`](/rules/tally/labels/no-buildx-git-overlap) * [`tally/labels/no-stale-base-digest`](/rules/tally/labels/no-stale-base-digest) * [`tally/labels/valid-key`](/rules/tally/labels/valid-key) # tally/labels/prefer-stable-order Source: https://tally.wharflab.com/rules/tally/labels/prefer-stable-order Reorder LABEL key/value pairs into a deterministic, human-readable order. A multi-pair `LABEL` block reads better when keys appear in a stable, logical order. This rule reorders pairs inside a single `LABEL` instruction so that related image metadata stays grouped and diffs stay small. | Property | Value | | -------- | -------------- | | Severity | Info | | Category | Style | | Default | Enabled | | Auto-fix | Yes (in-block) | ## Description Image metadata reads best when keys cluster by purpose. The default `oci-logical` order follows how reviewers usually scan a `LABEL` block: | Group | Keys | | --------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------ | | 1. Identity | `org.opencontainers.image.title`, `org.opencontainers.image.description` | | 2. Source / refs | `org.opencontainers.image.source`, `org.opencontainers.image.url`, `org.opencontainers.image.documentation` | | 3. Ownership / legal | `org.opencontainers.image.authors`, `org.opencontainers.image.vendor`, `org.opencontainers.image.licenses` | | 4. Release / provenance | `org.opencontainers.image.version`, `org.opencontainers.image.revision`, `org.opencontainers.image.created`, `org.opencontainers.image.ref.name` | | 5. Base image | `org.opencontainers.image.base.name`, `org.opencontainers.image.base.digest` | | 6. OpenShift / Kubernetes catalog | `io.k8s.display-name`, `io.k8s.description`, `io.openshift.tags`, `io.openshift.expose-services`, `io.openshift.s2i.scripts-url` | | 7. Docker ecosystem | `com.docker.image.source.entrypoint`, `com.docker.extension.*` | | 8. Legacy | `org.label-schema.*`, `maintainer` | | 9. Unknown reverse-DNS | preserved or namespace-clustered (see `sort-unknown`) | | 10. Unknown unqualified | preserved | The `lexical` order is a flat alphabetical comparator across all keys. The rule reports when a single multi-pair `LABEL` instruction has at least two pairs and the configured comparator says they are not in stable order. ## Auto-fix The fix swaps the source text of individual `key=value` spans inside the existing `LABEL`, leaving continuation backslashes, indentation, and any surrounding whitespace untouched. Per-pair edits are narrow on purpose so they can co-run with [`tally/labels/prefer-grouped`](/rules/tally/labels/prefer-grouped) and [`tally/newline-per-chained-call`](/rules/tally/newline-per-chained-call) without conflicts. The fix is suppressed when: * The `LABEL` is single-line multi-pair. Let [`tally/newline-per-chained-call`](/rules/tally/newline-per-chained-call) split it onto continuation lines first; the next lint pass then reorders. * A comment line splits the pairs. Comments mark intentional sections, and the v1 fixer never crosses them. * The block contains a duplicate key. Defer to [`tally/labels/no-duplicate-keys`](/rules/tally/labels/no-duplicate-keys); reordering duplicates would change the effective image metadata. * A pair has a dynamic key, an empty key, an expansion error, or uses the legacy `LABEL key value` form. ## Examples ### Bad ```dockerfile theme={null} FROM alpine:3.20 LABEL org.opencontainers.image.description="example image" \ org.opencontainers.image.source="https://github.com/example/demo" \ org.opencontainers.image.title="demo" ``` ### Good ```dockerfile theme={null} FROM alpine:3.20 LABEL org.opencontainers.image.title="demo" \ org.opencontainers.image.description="example image" \ org.opencontainers.image.source="https://github.com/example/demo" ``` ## Configuration ```toml theme={null} [rules.tally.labels.prefer-stable-order] severity = "info" order = "oci-logical" sort-unknown = false ``` | Option | Type | Default | Description | | -------------- | ------------------------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `order` | `"oci-logical"` \| `"lexical"` | `"oci-logical"` | Comparator. `"oci-logical"` clusters keys by purpose. `"lexical"` sorts purely alphabetically. | | `sort-unknown` | boolean | `false` | When `true`, group 9 (unknown reverse-DNS keys) is clustered by namespace and sorted lexically within each namespace. When `false`, those keys keep their relative source order. Groups 1–8 are unaffected because their ordering is already fully specified, and group 10 (unqualified unknown keys) always preserves source order. | ## Related Rules * [`tally/labels/no-duplicate-keys`](/rules/tally/labels/no-duplicate-keys) — must run first when a duplicate would otherwise change the effective image metadata after reordering. * [`tally/labels/prefer-grouped`](/rules/tally/labels/prefer-grouped) — cooperative: combines scattered `LABEL` instructions into one multi-line block; this rule then reorders the pairs inside that block. * [`tally/newline-per-chained-call`](/rules/tally/newline-per-chained-call) — splits single-line multi-pair `LABEL` instructions onto continuation lines so this rule can reorder them on the next lint pass. * [`tally/labels/valid-key`](/rules/tally/labels/valid-key) * [`tally/labels/no-buildx-git-overlap`](/rules/tally/labels/no-buildx-git-overlap) * [`tally/labels/no-stale-base-digest`](/rules/tally/labels/no-stale-base-digest) # tally/labels/valid-key Source: https://tally.wharflab.com/rules/tally/labels/valid-key LABEL keys should follow Docker's documented key format and avoid reserved Docker namespaces. LABEL keys should follow Docker's documented key format and avoid reserved Docker namespaces. | Property | Value | | -------- | ----------- | | Severity | Warning | | Category | Correctness | | Default | Enabled | | Auto-fix | No | ## Description Docker recommends label keys use lower-case alphanumeric characters, periods, and hyphens. Labels intended for wider reuse should use reverse-DNS prefixes such as `org.opencontainers.image.source` or `com.example.team.owner` so independent tools do not collide. This rule reports keys with whitespace, uppercase characters, unsupported punctuation, repeated separators, missing alphanumeric boundaries, or Docker reserved namespaces such as `com.docker.*`, `io.docker.*`, and `org.dockerproject.*`. The rule allows known Docker-owned keys that appear in normal Docker workflows, including `com.docker.image.source.entrypoint` and Docker extension namespaces. Dynamic keys are reported at `info` severity because they prevent static checks from validating keys and finding duplicates. The old `LABEL key value` form is left to BuildKit's `LegacyKeyValueFormat` rule. ## Examples ### Bad ```dockerfile theme={null} FROM alpine:3.20 LABEL "bad key"=value LABEL Bad.Key=value LABEL bad/key=value LABEL com.docker.compose.project=demo LABEL "$LABEL_PREFIX.name"=demo ``` ### Good ```dockerfile theme={null} FROM alpine:3.20 LABEL org.opencontainers.image.title="demo" \ org.opencontainers.image.source="https://github.com/example/demo" \ com.example.team.owner="platform" ``` ## Configuration No custom configuration options. The rule is enabled by default with severity `warning`. ```toml theme={null} [rules.tally.labels.valid-key] severity = "off" ``` ## References * [Docker object labels](https://docs.docker.com/engine/manage-resources/labels/) * [OCI image annotations](https://github.com/opencontainers/image-spec/blob/main/annotations.md) ## Related Rules * [`buildkit/LegacyKeyValueFormat`](/rules/buildkit/LegacyKeyValueFormat) * [`tally/labels/no-buildx-git-overlap`](/rules/tally/labels/no-buildx-git-overlap) * [`tally/labels/no-duplicate-keys`](/rules/tally/labels/no-duplicate-keys) * [`tally/labels/no-stale-base-digest`](/rules/tally/labels/no-stale-base-digest) # tally/max-lines Source: https://tally.wharflab.com/rules/tally/max-lines Enforces maximum number of lines in a Dockerfile. Enforces maximum number of lines in a Dockerfile. | Property | Value | | -------- | ------------------ | | Severity | Error | | Category | Maintainability | | Default | Enabled (50 lines) | ## Description Limits Dockerfile size to encourage modular builds. Enabled by default with a 50-line limit (P90 of analyzed public Dockerfiles). Large Dockerfiles are harder to maintain, review, and debug. This rule encourages: * Breaking complex builds into multi-stage patterns * Using base images for common dependencies * Keeping build logic modular ## Options | Option | Type | Default | Description | | ------------------ | ------- | ------- | -------------------------------- | | `max` | integer | 50 | Maximum lines allowed | | `skip-blank-lines` | boolean | true | Exclude blank lines from count | | `skip-comments` | boolean | true | Exclude comment lines from count | ## Examples ### Bad ```dockerfile theme={null} # A 100+ line Dockerfile with everything in one file FROM ubuntu:22.04 RUN apt-get update && apt-get install -y \ build-essential \ curl \ # ... 50 more packages ... vim # ... 80 more lines of setup ... ``` ### Good ```dockerfile theme={null} # Base image with common dependencies FROM myorg/base:1.0 # Application-specific setup only COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD ["python", "app.py"] ``` ## Configuration ```toml theme={null} [rules.tally.max-lines] severity = "warning" max = 100 skip-blank-lines = true skip-comments = true ``` ## CLI Flags ```bash theme={null} tally lint --max-lines 100 --skip-blank-lines --skip-comments Dockerfile ``` # tally/named-identity-in-passwdless-stage Source: https://tally.wharflab.com/rules/tally/named-identity-in-passwdless-stage Named user/group in USER or --chown requires /etc/passwd which passwd-less stages lack. Named user/group in USER or --chown requires /etc/passwd which passwd-less stages lack. | Property | Value | | -------- | ----------- | | Severity | Warning | | Category | Correctness | | Default | Enabled | ## Description Detects named (non-numeric) user or group references in `USER` instructions or `COPY`/`ADD --chown` flags within stages that lack `/etc/passwd` or `/etc/group`. Named identity resolution requires these database files; without them, the build will fail at `RUN` time or the runtime will reject the container. This is a common pitfall in `scratch` and multi-stage builds that inherit from scratch without copying the passwd/group databases from a builder stage. Numeric UIDs/GIDs (e.g., `65532`, `1000:1000`) work without any passwd database and are the recommended approach for minimal images. The rule suppresses after a `SHELL` instruction, since the user may have bootstrapped tools that handle identity resolution. ## Examples ### Bad ```dockerfile theme={null} FROM scratch COPY --from=builder /myapp /myapp USER appuser ``` ```dockerfile theme={null} FROM scratch COPY --chown=appuser:appgroup --from=builder /myapp /myapp ``` ### Good (numeric IDs) ```dockerfile theme={null} FROM scratch COPY --from=builder /myapp /myapp USER 65532:65532 ``` ```dockerfile theme={null} FROM scratch COPY --chown=1000:1000 --from=builder /myapp /myapp ``` ### Good (passwd copied from builder) ```dockerfile theme={null} FROM golang:1.22 AS builder RUN useradd -r appuser FROM scratch COPY --from=builder /etc/passwd /etc/passwd COPY --from=builder /etc/group /etc/group COPY --from=builder /myapp /myapp USER appuser ``` ### Good (non-scratch base image) ```dockerfile theme={null} FROM alpine:3.19 RUN adduser -D appuser USER appuser ``` ## Suggested fix The rule suggests replacing named identities with the numeric UID/GID `65532` (the conventional non-root ID used by distroless and Chainguard images). This fix uses `FixSuggestion` safety because the numeric ID may not match the author's intended user. Alternatively, copy `/etc/passwd` and `/etc/group` from a builder stage that has the desired user. ## Related rules * [`tally/shell-run-in-scratch`](./shell-run-in-scratch) -- detects shell-form RUN in scratch stages (different concern: shell availability vs identity resolution) * [`tally/copy-after-user-without-chown`](./copy-after-user-without-chown) -- detects missing `--chown` after `USER` (complementary: different condition) * [`tally/user-created-but-never-used`](./user-created-but-never-used) -- detects created users that are never switched to (complementary: different condition) ## Configuration ```toml theme={null} [rules.tally.named-identity-in-passwdless-stage] severity = "warning" # Options: "off", "error", "warning", "info", "style" ``` # tally/newline-between-instructions Source: https://tally.wharflab.com/rules/tally/newline-between-instructions Controls blank lines between Dockerfile instructions. Controls blank lines between Dockerfile instructions. | Property | Value | | -------- | ----------------- | | Severity | Style | | Category | Style | | Default | Enabled (grouped) | | Auto-fix | Yes (safe) | ## Description Enforces consistent blank-line spacing between Dockerfile instructions. Three modes are available: * **`grouped`** (default): Same instruction types are grouped together with no blank lines between them; different types are separated by exactly one blank line. * **`always`**: Every instruction is followed by at least one blank line. * **`never`**: All blank lines between instructions are removed. ### Grouped mode (default) ```dockerfile theme={null} FROM alpine:3.20 RUN apk add --no-cache curl ENV FOO=bar ENV BAZ=qux COPY . /app ``` Same-type instructions (both `ENV`) have no blank line between them. Different types (`FROM` to `RUN`, `RUN` to `ENV`, `ENV` to `COPY`) are separated by a blank line. ### Always mode ```dockerfile theme={null} FROM alpine:3.20 RUN apk add --no-cache curl ENV FOO=bar ENV BAZ=qux COPY . /app ``` ### Never mode ```dockerfile theme={null} FROM alpine:3.20 RUN apk add --no-cache curl ENV FOO=bar ENV BAZ=qux COPY . /app ``` ## Examples ### Bad (grouped mode, missing blank line) ```dockerfile theme={null} FROM alpine:3.20 RUN echo hello ``` ### Good (grouped mode) ```dockerfile theme={null} FROM alpine:3.20 RUN echo hello ``` ### Bad (grouped mode, unwanted blank between same types) ```dockerfile theme={null} ENV FOO=bar ENV BAZ=qux ``` ### Good (grouped mode, same types adjacent) ```dockerfile theme={null} ENV FOO=bar ENV BAZ=qux ``` ## Configuration Default (grouped mode, no config needed): ```toml theme={null} # Grouped mode is the default when the rule is enabled ``` Always mode: ```toml theme={null} [rules.tally.newline-between-instructions] mode = "always" ``` Never mode: ```toml theme={null} [rules.tally.newline-between-instructions] mode = "never" ``` String shorthand: ```toml theme={null} [rules.tally] newline-between-instructions = "always" ``` ## Auto-fix This rule provides safe auto-fixes: * **Insert blank line**: Adds a single blank line between instructions that need separation. * **Remove blank lines**: Removes excess blank lines between instructions that should be adjacent. ```bash theme={null} tally lint --fix Dockerfile ``` ## Interaction with buildkit/InvalidDefinitionDescription When both rules are enabled, `buildkit/InvalidDefinitionDescription` may insert blank lines between comments and instructions (to indicate a comment is not a description). These blank lines are **not** affected by `newline-between-instructions` because the newline rule only measures gaps between instruction nodes, not between comments and their associated instructions. For example, with `never` mode and `InvalidDefinitionDescription` enabled: ```dockerfile theme={null} # This comment doesn't describe the ARG ARG foo=bar FROM scratch AS base RUN echo hello ``` After auto-fix, `InvalidDefinitionDescription` inserts a blank line between the comment and `ARG`, while `newline-between-instructions` keeps instructions adjacent: ```dockerfile theme={null} # This comment doesn't describe the ARG ARG foo=bar FROM scratch AS base RUN echo hello ``` # tally/newline-per-chained-call Source: https://tally.wharflab.com/rules/tally/newline-per-chained-call Each chained element within an instruction should be on its own line. Each chained element within an instruction should be on its own line. | Property | Value | | -------- | ---------- | | Severity | Style | | Category | Style | | Default | Enabled | | Auto-fix | Yes (safe) | ## Description Enforces that chained elements within Dockerfile instructions are placed on separate continuation lines using `\`. This improves readability and produces cleaner diffs. Applies to three instruction types: * **RUN** -- splits `&&`/`||` chain boundaries AND splits multiple `--mount=` flags * **LABEL** -- splits multiple `key=value` pairs * **HEALTHCHECK CMD** -- splits `&&`/`||` chain boundaries (shell form only) ## Examples ### Bad ```dockerfile theme={null} RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* RUN --mount=type=cache,target=/var/cache/apt --mount=type=bind,source=go.sum,target=go.sum apt-get update LABEL org.opencontainers.image.title=myapp org.opencontainers.image.version=1.0 org.opencontainers.image.vendor=acme HEALTHCHECK CMD curl -f http://localhost/ && wget -qO- http://localhost/health || exit 1 ``` ### Good ```dockerfile theme={null} RUN apt-get update \ && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/* RUN --mount=type=cache,target=/var/cache/apt \ --mount=type=bind,source=go.sum,target=go.sum \ apt-get update LABEL org.opencontainers.image.title=myapp \ org.opencontainers.image.version=1.0 \ org.opencontainers.image.vendor=acme HEALTHCHECK CMD curl -f http://localhost/ \ && wget -qO- http://localhost/health \ || exit 1 ``` ## Configuration Default (no config needed): ```toml theme={null} # Enabled by default with min-commands = 2 ``` Require 3+ chained commands before splitting: ```toml theme={null} [rules.tally.newline-per-chained-call] min-commands = 3 ``` ## Options | Option | Type | Default | Description | | -------------- | ------- | ------- | ----------------------------------------------------------------------------------------------- | | `min-commands` | integer | `2` | Minimum chained commands to trigger chain splitting (>= 2). Applies to RUN and HEALTHCHECK CMD. | LABEL pair splitting and mount splitting always trigger when 2+ elements share a line (not affected by `min-commands`). ## Skipped Cases The rule skips the following: * **Exec form** (`RUN ["cmd"]`) -- no shell to parse * **Heredoc RUN** (`RUN <` * Is not the target of a `--target` build argument Unreachable stages add complexity and confusion without providing value. They may be: * Leftover from refactoring * Copy-paste artifacts * Forgotten experimental code ## Examples ### Bad ```dockerfile theme={null} # This stage is never used FROM golang:1.21 AS unused-builder RUN go build -o /app . # This is the actual build FROM golang:1.21 AS builder COPY . . RUN go build -o /app . FROM alpine:3.18 COPY --from=builder /app /app CMD ["/app"] ``` ### Good ```dockerfile theme={null} # All stages contribute to the final image FROM golang:1.21 AS builder COPY . . RUN go build -o /app . FROM alpine:3.18 COPY --from=builder /app /app CMD ["/app"] ``` ### Also Good (targeted builds) ```dockerfile theme={null} # Both stages are valid targets FROM golang:1.21 AS dev RUN go install github.com/cosmtrek/air@latest CMD ["air"] FROM golang:1.21 AS builder RUN go build -o /app . FROM alpine:3.18 AS prod COPY --from=builder /app /app CMD ["/app"] ``` ```bash theme={null} # Build for development docker build --target dev -t myapp:dev . # Build for production docker build --target prod -t myapp:prod . ``` ## Configuration ```toml theme={null} [rules.tally.no-unreachable-stages] severity = "warning" # Options: "off", "error", "warning", "info", "style" ``` # tally/php/composer-no-dev-in-production Source: https://tally.wharflab.com/rules/tally/php/composer-no-dev-in-production Production Composer install commands should include `--no-dev`. Production Composer install commands should include `--no-dev`. | Property | Value | | -------- | ----------------------------------------- | | Severity | Warning | | Category | Security | | Default | Enabled | | Auto-fix | Yes (suggestion, requires `--fix-unsafe`) | ## Description Flags `composer install` in production-like stages when dev dependencies are still included. Shipping `require-dev` packages in a production image increases the dependency graph, image size, and attack surface. The rule accepts either: * `composer install --no-dev` * `ENV COMPOSER_NO_DEV=1` earlier in the same stage Stages explicitly named `dev`, `development`, `test`, `testing`, `ci`, or `debug` are skipped. ## Examples ### Before ```dockerfile theme={null} FROM php:8.4-cli AS app WORKDIR /app COPY composer.json composer.lock ./ RUN composer install --no-interaction ``` ### After ```dockerfile theme={null} FROM php:8.4-cli AS app WORKDIR /app COPY composer.json composer.lock ./ RUN composer install --no-dev --no-interaction ``` ### Stage-level env equivalent ```dockerfile theme={null} FROM php:8.4-cli AS app ENV COMPOSER_NO_DEV=1 RUN composer install ``` ## Why this rule is a suggestion fix The edit is narrow, but it changes which dependencies are installed. That is the right default for production images, but it is still a behavior change, so the fix is classified as `FixSuggestion`. ## References * [Composer CLI: install](https://getcomposer.org/doc/03-cli.md#install-i) * [Docker guide for PHP: develop](https://docs.docker.com/guides/php/develop/) # tally/php/enable-opcache-in-production Source: https://tally.wharflab.com/rules/tally/php/enable-opcache-in-production Production PHP web runtime images should install and enable OPcache. Production PHP web runtime images should install and enable OPcache. | Property | Value | | -------- | --------------------------------------------------------------------- | | Severity | Info | | Category | Performance | | Default | Enabled | | Auto-fix | Yes (heuristic: official `php:*` images or single `php*-fpm` package) | ## What is OPcache? OPcache is a PHP extension that stores precompiled script bytecode in shared memory, removing the need for PHP to re-read and re-parse scripts on every request. Because only the low-level bytecode needs to run — no lexing, parsing, or compilation — it has a large, measurable impact on the request throughput of any real-world PHP application. When a request comes in: 1. PHP checks whether OPcache is enabled. 2. If the script's bytecode is already in the cache, OPcache returns it straight from shared memory and the Zend Engine executes it. The tokenize → parse → compile pipeline is skipped entirely. 3. If the bytecode is not cached, PHP processes the script as usual (tokenize, parse, compile to opcodes), stores the resulting bytecode in shared memory, and then executes it. Subsequent requests hit the cache. OPcache ships with PHP core and is the canonical production cache for PHP web runtimes. Enabling it is a one-line change with no application-code impact. ## Description Flags long-running PHP web runtime stages that do not install, enable, or configure OPcache. OPcache stores precompiled PHP bytecode in shared memory, avoiding per-request script compilation and delivering a substantial throughput improvement in production. The rule only triggers on the **final** build stage. A stage is treated as a PHP web runtime when any of the following hold: * the base image is an official `php:*` tag whose tag contains `fpm` or `apache` * the base image is a known PHP web runtime derivative (`serversideup/php`, `dunglas/frankenphp`, `bitnami/php-fpm`, `trafex/php-nginx`, `webdevops/php-nginx`, `webdevops/php-apache`) * the effective `ENTRYPOINT` or `CMD` starts a known PHP web server wrapper (`php-fpm`, `php-fpm7.4`, `php-fpm8.3`, `apache2-foreground`, `httpd-foreground`, `frankenphp`, `rr`) Stages explicitly named `dev`, `development`, `test`, `testing`, `ci`, or `debug` are skipped, as are non-final stages. CLI-only PHP images (`php:*-cli` with no PHP web server wrapper) are also skipped. ### Signals treated as compliant Any of the following in the final stage suppress the violation: * `docker-php-ext-install opcache`, `docker-php-ext-enable opcache`, or `docker-php-ext-configure opcache` * `pecl install opcache` * Package-manager installs referencing OPcache (`apt-get install php-opcache`, `apk add php83-opcache`, `dnf install php-opcache`, etc.), with version/architecture specifiers stripped (`php-opcache=8.3+1ubuntu1`, `php-opcache:amd64`) * An image file that looks like an OPcache ini (`*opcache*.ini`) or whose content enables OPcache via `opcache.*` keys (e.g., `opcache.enable=1`) or `zend_extension=opcache` * A stage-level environment variable prefixed with `PHP_OPCACHE_` ## Fix behavior The rule offers two heuristic `FixSuggestion` fixes: ### 1. Official `php:*fpm*` / `*apache*` base When the base image is an official `php:*fpm*` or `php:*apache*` Linux tag, the rule inserts a new `RUN docker-php-ext-install opcache` line immediately after the final `FROM`. The edit is narrow, zero-width at the insertion column, and does not modify any existing lines. ### 2. Generic base that installs PHP via a package manager When the final stage is detected as PHP only via `CMD`/`ENTRYPOINT` (e.g., `debian:12-slim` + `CMD ["php-fpm8.3"]`) **and** the stage's install commands include exactly one `php*-fpm` package, the rule appends the matching `php*-opcache` package to that same install command. The matching package name is derived directly from the existing PHP package the user chose: | Package in `RUN` | Derived OPcache package | | ---------------------------- | ----------------------- | | `php8.3-fpm` (Debian/Ubuntu) | `php8.3-opcache` | | `php-fpm` (unversioned) | `php-opcache` | | `php83-fpm` (Alpine) | `php83-opcache` | | `php83-php-fpm` (Remi SCL) | `php83-php-opcache` | Example: ```dockerfile theme={null} # Before RUN apt-get install -y php8.3-fpm # After RUN apt-get install -y php8.3-fpm php8.3-opcache ``` ### No fix is offered * the base image is a non-official derivative (`serversideup/php`, `dunglas/frankenphp`, `bitnami/php-fpm`, ...) — they usually bundle OPcache or expect different ini paths, and the right remediation is image-specific * the stage is detected via `CMD`/`ENTRYPOINT` but no `php*-fpm` package is visible in an install command in the same Dockerfile * the stage installs **multiple different** `php*-fpm` packages (ambiguous target version) The package-manager fix coordinates with `tally/sort-packages`: when both rules are enabled, the inserted opcache package is appended at the end of the install command so that sort-packages produces a correctly ordered list in the same `--fix` pass. ## Examples ### Before ```dockerfile theme={null} FROM php:8.4-fpm WORKDIR /app COPY . . ``` ### After ```dockerfile theme={null} FROM php:8.4-fpm RUN docker-php-ext-install opcache WORKDIR /app COPY . . ``` ## References * [PHP OPcache manual](https://www.php.net/manual/en/book.opcache.php) * [Docker guide for PHP: develop](https://docs.docker.com/guides/php/develop/) * [Composer autoloader optimization](https://getcomposer.org/doc/articles/autoloader-optimization.md) # tally/php/no-xdebug-in-final-image Source: https://tally.wharflab.com/rules/tally/php/no-xdebug-in-final-image Final image installs or enables Xdebug, a development-only tool. Final image installs or enables Xdebug, a development-only tool. | Property | Value | | -------- | ------------------------------------------------- | | Severity | Warning | | Category | Best Practices | | Default | Enabled | | Auto-fix | Yes (comment-out as suggestion, delete as unsafe) | ## Description Flags Xdebug installations in the final image stage. Xdebug is a PHP debugging and profiling tool designed for development workflows. Shipping it in production images degrades performance, increases image size, and widens the attack surface. The rule detects: * `docker-php-ext-install xdebug` * `docker-php-ext-enable xdebug` * `pecl install xdebug` (including versioned forms like `xdebug-3.4.0`) * Package manager installs containing xdebug (`apt-get install php-xdebug`, `apk add php-pecl-xdebug`, etc.) * Observable scripts (COPY heredoc, build context) that install Xdebug Stages explicitly named `dev`, `development`, `test`, `testing`, `ci`, or `debug` are skipped. Only the final stage is checked — intermediate builder stages are expected to have development tooling. ## Examples ### Before ```dockerfile theme={null} FROM php:8.4-fpm AS app WORKDIR /app COPY . . RUN docker-php-ext-install gd intl RUN pecl install xdebug && docker-php-ext-enable xdebug ``` ### After Move Xdebug into a dedicated development stage: ```dockerfile theme={null} FROM php:8.4-fpm AS app WORKDIR /app COPY . . RUN docker-php-ext-install gd intl FROM app AS dev RUN pecl install xdebug && docker-php-ext-enable xdebug ``` ## Fix behavior When the entire RUN instruction only installs or enables Xdebug, two alternative fixes are offered: 1. **Comment out** (suggestion, preferred): Prefixes each line with `#`. 2. **Delete** (unsafe): Removes the instruction entirely. When Xdebug is mixed with other extensions in the same command (e.g., `docker-php-ext-install gd xdebug intl`), no auto-fix is offered — the rule reports the violation for manual resolution. ## References * [Docker guide for PHP: develop](https://docs.docker.com/guides/php/develop/) * [Laravel Sail: Xdebug](https://laravel.com/docs/12.x/sail) * [PHP OPcache manual](https://www.php.net/manual/en/book.opcache.php) # tally/platform-mismatch Source: https://tally.wharflab.com/rules/tally/platform-mismatch Explicit `--platform` on FROM does not match what the registry provides. Explicit `--platform` on FROM does not match what the registry provides. | Property | Value | | -------- | ------------------------------------- | | Severity | Error | | Category | Correctness | | Default | Enabled | | Requires | `--slow-checks=on` (registry queries) | ## Description When a `FROM` instruction uses an explicit `--platform` flag, this rule queries the container registry to verify that the requested platform is actually available for the specified image. This catches provable mismatches before they fail at build time. Unlike `buildkit/InvalidBaseImagePlatform`, this rule: * **Only fires when `--platform` is explicitly set** on the `FROM` instruction * **Never compares against the host platform**, so results are deterministic across machines * **Skips automatic build args** (`$BUILDPLATFORM`, `$TARGETPLATFORM`, etc.) which are dynamic ## When it fires | Scenario | Result | | -------------------------------------------------------------------------------- | ---------------------- | | `FROM --platform=linux/arm64 image:tag` and registry has `linux/arm64` | No violation | | `FROM --platform=linux/arm64 image:tag` and registry does NOT have `linux/arm64` | **Violation** | | `FROM image:tag` (no `--platform`) | No violation | | `FROM --platform=$BUILDPLATFORM image:tag` | No violation (dynamic) | | `FROM --platform=$TARGETPLATFORM image:tag` | No violation (dynamic) | ## Examples ### Bad ```dockerfile theme={null} # python:3.12 is only published for linux/arm64 but linux/amd64 is requested FROM --platform=linux/amd64 python:3.12 RUN pip install flask ``` ### Good ```dockerfile theme={null} # No --platform: builder picks the right platform at build time FROM python:3.12 RUN pip install flask ``` ```dockerfile theme={null} # Correct platform that the image actually provides FROM --platform=linux/arm64 python:3.12 RUN pip install flask ``` ```dockerfile theme={null} # Dynamic platform via build arg FROM --platform=$BUILDPLATFORM golang:1.22 AS builder RUN go build -o /app ``` ## Relationship to other rules * **`buildkit/InvalidBaseImagePlatform`** (default: Off) — the BuildKit rule compares against the host platform even without `--platform`, producing non-deterministic results. This rule supersedes it with a stricter, deterministic approach. * **`buildkit/FromPlatformFlagConstDisallowed`** (default: Off) — the BuildKit rule warns on any constant `--platform` value. This is too strict: hardcoded `--platform` is legitimate for ARM-only services, Windows containers, and cross-compilation. The new rule validates the platform against the registry instead of discouraging it. # tally/powershell/error-action-preference Source: https://tally.wharflab.com/rules/tally/powershell/error-action-preference Require `$ErrorActionPreference = 'Stop'` and `$PSNativeCommandUseErrorActionPreference = $true` in PowerShell RUN instructions. Require `$ErrorActionPreference = 'Stop'` and `$PSNativeCommandUseErrorActionPreference = $true` in PowerShell `RUN` instructions. | Property | Value | | -------- | -------------------------- | | Severity | Warning | | Category | Correctness | | Default | Enabled | | Auto-fix | Yes (`--fix --fix-unsafe`) | ## Description This rule detects PowerShell `RUN` instructions that lack fail-fast error handling. It checks for two related preferences: 1. **`$ErrorActionPreference = 'Stop'`** -- catches non-terminating PowerShell cmdlet errors that would otherwise be silently swallowed. 2. **`$PSNativeCommandUseErrorActionPreference = $true`** -- extends error handling to native command exit codes (e.g., `git`, `dotnet`, `curl`) in PowerShell 7.3+. The rule fires on both Linux and Windows stages whenever PowerShell is the effective shell (via `SHELL` instruction or explicit `powershell -Command` / `pwsh -Command` wrappers). ## Why this matters PowerShell does not fail-fast by default. Without `$ErrorActionPreference = 'Stop'`, an intermediate `Invoke-WebRequest` can fail silently, then `Start-Process` runs on a missing installer, then `Remove-Item` succeeds because nothing was there. The cascading silent failures are the real danger in multi-statement Docker build steps. The `$PSNativeCommandUseErrorActionPreference` variable was added in PowerShell 7.3 to close a gap: even with `$ErrorActionPreference = 'Stop'`, non-zero exit codes from native executables were ignored. Setting it to `$true` extends the fail-fast behavior to all commands. ## Examples ### Before (violation) ```dockerfile theme={null} FROM mcr.microsoft.com/powershell:ubuntu-22.04 SHELL ["pwsh", "-Command"] RUN Install-Module PSReadLine -Force; Write-Host "done" ``` ### After (fixed with `--fix --fix-unsafe`) ```dockerfile theme={null} FROM mcr.microsoft.com/powershell:ubuntu-22.04 SHELL ["pwsh", "-Command", "$ErrorActionPreference = 'Stop'; $PSNativeCommandUseErrorActionPreference = $true;"] RUN Install-Module PSReadLine -Force; Write-Host "done" ``` ### Already clean (no violation) ```dockerfile theme={null} FROM mcr.microsoft.com/powershell:ubuntu-22.04 SHELL ["pwsh", "-Command", "$ErrorActionPreference = 'Stop'; $PSNativeCommandUseErrorActionPreference = $true; $ProgressPreference = 'SilentlyContinue';"] RUN Install-Module PSReadLine -Force; Write-Host "done" ``` ## Configuration ### `min-statements` Minimum number of PowerShell statements in a `RUN` to trigger the rule. * **Type:** integer * **Default:** `2` * **Minimum:** `1` Set to `1` to also catch non-terminating error swallowing on single-command `RUN` instructions. ```toml theme={null} [rules.tally.powershell.error-action-preference] min-statements = 1 ``` ## Fix behavior The auto-fix injects whichever preferences are missing. The strategy depends on the shell context: * **Existing `SHELL` instruction**: appends the missing preferences to the last argument. * **No `SHELL` instruction**: inserts a new `SHELL` instruction after the `FROM`. * **Explicit wrapper** (`RUN powershell -Command ...`): inserts the missing preferences at the start of the inner script, right before the first command. The fix uses `FixSuggestion` safety, requiring `--fix-unsafe` to apply. ## Interaction with other rules * **`tally/powershell/prefer-shell-instruction`** (priority 95): runs first. If it inserts a SHELL with the full prelude, the error-action-preference fix is skipped as overlapping. * **`tally/prefer-run-heredoc`** (priority 100): when converting multi-statement RUNs to heredocs, the heredoc formatter automatically injects both preferences if missing. ## References * [`$ErrorActionPreference`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables#erroractionpreference) \-- Microsoft Learn: controls how PowerShell responds to non-terminating errors. Default is `Continue` (silently swallow); `Stop` converts them to terminating errors. * [`$PSNativeCommandUseErrorActionPreference`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables#psnativecommanduserroractionpreference) \-- Microsoft Learn: when `$true`, non-zero exit codes from native commands are treated as errors according to `$ErrorActionPreference`. Added in PowerShell 7.3; default is `$false`. # tally/powershell/prefer-shell-instruction Source: https://tally.wharflab.com/rules/tally/powershell/prefer-shell-instruction Prefer a `SHELL` instruction over repeating `pwsh` or `powershell` wrappers in `RUN`. Prefer a `SHELL` instruction over repeating `pwsh` or `powershell` wrappers in `RUN`. | Property | Value | | -------- | -------------------------- | | Severity | Style | | Category | Style | | Default | Enabled (experimental) | | Auto-fix | Yes (`--fix --fix-unsafe`) | ## Description This rule detects repeated shell-form `RUN` instructions that invoke PowerShell explicitly, for example: * `RUN pwsh -Command ...` * `RUN powershell -Command ...` * `RUN @powershell -Command ...` When PowerShell is already the effective shell via a preceding `SHELL [...]` instruction, the rule does nothing. The recommendation applies on both Windows and Linux. A Linux image such as `mcr.microsoft.com/powershell:ubuntu-22.04` still benefits from switching to a PowerShell `SHELL` once multiple PowerShell `RUN` commands appear. ## Why this matters Repeating the full wrapper on every `RUN` line adds noise and makes PowerShell-specific defaults easy to forget. A dedicated `SHELL` instruction: * makes repeated PowerShell build steps easier to read * centralizes the shell choice instead of duplicating it across `RUN`s * lets tally inject sane build defaults once: * `$ErrorActionPreference = 'Stop'` * `$PSNativeCommandUseErrorActionPreference = $true` * `$ProgressPreference = 'SilentlyContinue'` ## Examples ### Before (violation) ```dockerfile theme={null} FROM mcr.microsoft.com/powershell:ubuntu-22.04 RUN pwsh -NoLogo -NoProfile -Command Install-Module PSReadLine -Force ENV POWERSHELL_TELEMETRY_OPTOUT=1 RUN pwsh -NoLogo -NoProfile -Command Invoke-WebRequest https://example.com/tools.zip -OutFile /tmp/tools.zip ``` ### After (fixed with `--fix --fix-unsafe`) ```dockerfile theme={null} FROM mcr.microsoft.com/powershell:ubuntu-22.04 SHELL ["pwsh", "-NoLogo", "-NoProfile", "-Command", "$ErrorActionPreference = 'Stop'; $PSNativeCommandUseErrorActionPreference = $true; $ProgressPreference = 'SilentlyContinue';"] RUN Install-Module PSReadLine -Force ENV POWERSHELL_TELEMETRY_OPTOUT=1 RUN Invoke-WebRequest https://example.com/tools.zip -OutFile /tmp/tools.zip ``` ### Windows example ```dockerfile theme={null} FROM mcr.microsoft.com/windows/servercore:ltsc2022 RUN powershell -Command Invoke-WebRequest https://example.com/file.zip -OutFile C:\temp\file.zip RUN powershell -Command Expand-Archive C:\temp\file.zip -DestinationPath C:\tools ``` becomes: ```dockerfile theme={null} FROM mcr.microsoft.com/windows/servercore:ltsc2022 SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $PSNativeCommandUseErrorActionPreference = $true; $ProgressPreference = 'SilentlyContinue';"] RUN Invoke-WebRequest https://example.com/file.zip -OutFile C:\temp\file.zip RUN Expand-Archive C:\temp\file.zip -DestinationPath C:\tools ``` ## Configuration This rule has no rule-specific options today. ```toml theme={null} [rules.tally.powershell.prefer-shell-instruction] severity = "style" ``` ## Fix behavior The fixer is intentionally conservative. It only rewrites repeated PowerShell wrappers when the repeated `RUN` instructions share the same executable and the same arguments before `-Command` (for example, repeated `pwsh -NoProfile -Command ...`). On Windows container stages, the fixer also collaborates with `tally/prefer-run-heredoc`: * it can qualify bare `RUN powershell ...` chains, not only explicit `-Command` wrappers * when a `cmd` stage is converted to a PowerShell `SHELL`, later PowerShell-safe `RUN` instructions can stay under that shell instead of forcing an immediate restore to `cmd` * after that rewrite, `tally/prefer-run-heredoc` can merge the resulting PowerShell `RUN` sequence into a PowerShell heredoc in the same fix pass ## References * [PowerShell Docker image](https://mcr.microsoft.com/en-us/product/powershell/about) # tally/powershell/progress-preference Source: https://tally.wharflab.com/rules/tally/powershell/progress-preference Suppress PowerShell progress bars before `Invoke-WebRequest` by setting `$ProgressPreference = 'SilentlyContinue'`. Suppress PowerShell progress bars before `Invoke-WebRequest` (or its alias `iwr`) by setting `$ProgressPreference = 'SilentlyContinue'`. | Property | Value | | -------- | -------------------------- | | Severity | Style | | Category | Style | | Default | Enabled | | Auto-fix | Yes (`--fix --fix-unsafe`) | ## Description PowerShell's `Invoke-WebRequest` renders a per-response progress bar by default. On Windows containers this collapses download throughput by an order of magnitude because the host cannot render the bar and the cmdlet stalls waiting for terminal updates. Microsoft's official Windows container guidance is to set `$ProgressPreference = 'SilentlyContinue'` before any `Invoke-WebRequest` call. The rule fires whenever a `RUN` invokes `Invoke-WebRequest` or `iwr` without a preceding `$ProgressPreference = 'SilentlyContinue'` assignment. Detection does not depend on the stage's default shell — `Invoke-WebRequest` is a PowerShell-unique cmdlet, so finding it is itself the shell signal. That way the rule catches both SHELL-based PowerShell stages and one-off `powershell -Command` wrappers embedded inside bash stages. ## Why this matters On Windows Server Core images specifically, users have reported `Invoke-WebRequest` downloads taking minutes where a `curl` or the same call with `$ProgressPreference = 'SilentlyContinue'` takes seconds. The progress bar is pure noise in a non-interactive build step. ## Examples ### Before (violation) ```dockerfile theme={null} FROM mcr.microsoft.com/powershell:ubuntu-22.04 SHELL ["pwsh", "-Command"] RUN Invoke-WebRequest https://example.com/file.zip -OutFile /tmp/file.zip ``` ### After (fixed with `--fix --fix-unsafe`) ```dockerfile theme={null} FROM mcr.microsoft.com/powershell:ubuntu-22.04 SHELL ["pwsh", "-Command", "$ProgressPreference = 'SilentlyContinue';"] RUN Invoke-WebRequest https://example.com/file.zip -OutFile /tmp/file.zip ``` ### Already clean (no violation) ```dockerfile theme={null} FROM mcr.microsoft.com/powershell:ubuntu-22.04 SHELL ["pwsh", "-Command", "$ErrorActionPreference = 'Stop'; $PSNativeCommandUseErrorActionPreference = $true; $ProgressPreference = 'SilentlyContinue';"] RUN Invoke-WebRequest https://example.com/file.zip -OutFile /tmp/file.zip ``` ### Explicit wrapper (violation) ```dockerfile theme={null} FROM ubuntu:22.04 RUN pwsh -Command "Invoke-WebRequest https://example.com/file.zip -OutFile /tmp/file.zip" ``` The fix prepends the assignment to the inner script: ```dockerfile theme={null} FROM ubuntu:22.04 RUN pwsh -Command "$ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest https://example.com/file.zip -OutFile /tmp/file.zip" ``` ## Fix behavior The fix adapts to where the PowerShell script lives: * **`SHELL` with bare `-Command`**: appends a new array element `"$ProgressPreference = 'SilentlyContinue';"` to the `SHELL` instruction. * **`SHELL` with an existing prelude**: appends the assignment to the existing prelude string, inserting a `;` separator if missing. * **No `SHELL` in a PowerShell-by-default stage**: inserts a new `SHELL` instruction after the `FROM`. * **Explicit `powershell -Command` / `pwsh -Command` wrapper**: zero-width insertion at the start of the inner script. * **`RUN <` over `git clone` inside `RUN`. Prefer `ADD ` over `git clone` inside `RUN`. | Property | Value | | -------- | -------------------------- | | Severity | Warning | | Category | Security | | Default | Enabled | | Auto-fix | Yes (`--fix --fix-unsafe`) | ## Description Flags `RUN` instructions that fetch source code with `git clone`, recommending BuildKit git sources such as [`ADD --link https://github.com/user/repo.git /src/repo`](https://docs.docker.com/reference/dockerfile/#add). Moving repository acquisition out of `RUN` makes the fetch explicit in the Dockerfile dependency graph, reduces mutable network behavior inside shell steps, and improves hermeticity for supply-chain-sensitive builds. ## Detected Patterns The rule reports remote `git clone` usage in shell-form `RUN` instructions, including: 1. Plain clones: `RUN git clone https://github.com/NVIDIA/apex` 2. Branch or tag selection: `RUN git clone https://github.com/aws/aws-ofi-nccl.git -b v${BRANCH_OFI}` 3. Clone flows in chained commands: `RUN echo foo && git clone ... && cd repo && git checkout && make` 4. GitLab HTTP remotes that need the generic selector form: `RUN git clone https://gitlab.haskell.org/haskell-wasm/ghc-wasm-meta.git -b ${GHC_WASM_META_COMMIT}` ## Examples ### Before (violation) ```dockerfile theme={null} FROM alpine:3.20 RUN git clone https://github.com/NVIDIA/apex RUN echo before && git clone https://github.com/NVIDIA/apex && cd apex && git checkout 0123456789abcdef0123456789abcdef01234567 && echo after ``` ### After (fixed with --fix --fix-unsafe) ```dockerfile theme={null} FROM alpine:3.20 ADD --link https://github.com/NVIDIA/apex.git /apex RUN echo before ADD --link --checksum=0123456789abcdef0123456789abcdef01234567 https://github.com/NVIDIA/apex.git?ref=0123456789abcdef0123456789abcdef01234567 /apex RUN cd /apex && echo after ``` ## Auto-fix Conditions The rule emits a sync `FixSuggestion` when it can safely isolate the clone flow into: * optional leading `RUN` commands that stay before the fetch * one `ADD ` * optional trailing `RUN` commands that continue after the fetch Current auto-fix coverage supports: * simple POSIX shell-form `RUN` instructions * `&&` chains where the clone flow can be isolated cleanly * optional `-b` / `--branch` * optional explicit destination directory * optional `cd ` followed by `git checkout ` * optional recursive clone flags, mapped to `submodules=true` * GitLab HTTP remotes via the generic `?ref=` selector form * `ADD --link` for better cache reuse on extracted git-source layers * `ADD --keep-git-dir=true` when later commands in the rewritten flow still run `git` * `ADD --checksum=` when the selected ref is a full commit ID ## Report-Only Cases The rule still reports, but does not auto-fix, when the clone appears in a shape that currently cannot be rewritten without dropping execution context, such as: * `RUN` instructions with non-mount BuildKit flags like `--network=...` * `RUN` instructions using mounts * complex shell constructs outside a simple `&&` chain * abbreviated hex `git checkout` values like `aa756ce`, because BuildKit git URLs safely encode full commit IDs, not abbreviated checkout SHAs * clone flows with unsupported git flags or unresolved destination paths ## Limitations * Current auto-fix targets POSIX shell parsing; non-POSIX shells are report-only * The generated fix uses BuildKit git-source URLs, so it requires BuildKit-enabled builds * The fixer emits `ref=` as the git-source selector. It does not guess between `branch=` and `tag=` because `git clone -b ` can refer to either one. * When the selected ref is a full commit ID, the fixer emits `--checksum=` as a verifier. * The fixer only rewrites the first clone flow in a matching `RUN`; additional clone flows can be fixed on a later run ## References * [Dockerfile `ADD` reference](https://docs.docker.com/reference/dockerfile/#add) * [Docker build context git URL queries (`branch`, `ref`, `commit`, `submodules`)](https://docs.docker.com/build/concepts/context/#url-queries) * [GitLab remote URL format using `ref=` selectors](https://docs.gitlab.com/editor_extensions/visual_studio_code/remote_urls/) # tally/prefer-add-unpack Source: https://tally.wharflab.com/rules/tally/prefer-add-unpack Prefer `ADD --unpack` for downloading and extracting remote archives. Prefer `ADD --unpack` for downloading and extracting remote archives. | Property | Value | | -------- | -------------------------- | | Severity | Info | | Category | Performance | | Default | Enabled | | Auto-fix | Yes (`--fix --fix-unsafe`) | ## Description Flags `RUN` instructions that download a remote tar archive with `curl` / `wget`, Windows `curl.exe` / `wget.exe`, or PowerShell `Invoke-WebRequest` / `iwr`, and extract it with `tar`, suggesting [`ADD --unpack `](https://docs.docker.com/reference/dockerfile/#add---unpack) instead. `ADD --unpack` is a [BuildKit feature](https://docs.docker.com/build/buildkit/) that downloads and extracts a remote tar archive in a single layer, reducing image size and build complexity. It is implemented directly in BuildKit's Go codepath, so it works on Windows containers too and avoids spawning download and extraction processes inside the build container. ## Detected Patterns 1. **Pipe pattern**: `curl -fsSL | tar -xz -C /dest` 2. **Download-then-extract**: `curl -o /tmp/app.tar.gz && tar -xf /tmp/app.tar.gz -C /dest` 3. **wget variants**: Same patterns with `wget` instead of `curl` 4. **Windows cmd variants**: `curl.exe ... -o C:\tmp\app.tar.gz && tar.exe -xf C:\tmp\app.tar.gz -C C:\tools` 5. **PowerShell variants**: `Invoke-WebRequest ... -OutFile C:\tmp\app.tar.gz; tar.exe -xf C:\tmp\app.tar.gz -C C:\tools` The rule checks that the URL has a recognized archive extension and that a `tar` extraction command is present in the same `RUN` instruction. ## Examples ### Before (violation) ```dockerfile theme={null} FROM ubuntu:22.04 RUN curl -fsSL https://go.dev/dl/go1.22.0.linux-amd64.tar.gz | tar -xz -C /usr/local RUN wget -O /tmp/node.tar.xz https://nodejs.org/dist/v20.11.0/node-v20.11.0-linux-x64.tar.xz && \ tar -xJf /tmp/node.tar.xz -C /usr/local --strip-components=1 FROM mcr.microsoft.com/windows/servercore:ltsc2022 SHELL ["powershell", "-Command"] RUN Invoke-WebRequest https://example.com/app.tar.gz -OutFile C:\tmp\app.tar.gz; tar.exe -xf C:\tmp\app.tar.gz -C C:\tools ``` ### After (fixed with --fix --fix-unsafe) ```dockerfile theme={null} FROM ubuntu:22.04 ADD --unpack https://go.dev/dl/go1.22.0.linux-amd64.tar.gz /usr/local ADD --unpack https://nodejs.org/dist/v20.11.0/node-v20.11.0-linux-x64.tar.xz /usr/local FROM mcr.microsoft.com/windows/servercore:ltsc2022 SHELL ["powershell", "-Command"] ADD --unpack https://example.com/app.tar.gz C:\tools ``` ## Auto-fix Conditions The auto-fix is only emitted when: * The `RUN` instruction contains **only** download and extraction commands (`curl` / `wget` / `curl.exe` / `wget.exe` / `Invoke-WebRequest` / `iwr` + `tar`) * A `tar` extraction command is present (`ADD --unpack` only handles tar archives) If additional commands are present (e.g. `chmod`, `rm`, `mv`), the violation is still reported but no fix is suggested, since those commands would be lost. The tar destination is extracted from `-C`, `--directory=`, or `--directory` flags. If no destination is specified, the effective `WORKDIR` is used. ## Limitations * PowerShell and Windows support is limited to download-then-extract patterns; POSIX-style pipe detection remains POSIX-shell-only * Only detects `tar` extraction (`ADD --unpack` does not handle single-file decompressors) * Does not match ZIP-oriented flows such as `Expand-Archive` * URL must have a recognized archive file extension ## Options | Option | Type | Default | Description | | --------- | ------- | ------- | -------------------------- | | `enabled` | boolean | true | Enable or disable the rule | ## Configuration ```toml theme={null} [rules.tally.prefer-add-unpack] enabled = true ``` ## References * [Dockerfile `ADD` reference](https://docs.docker.com/reference/dockerfile/#add) * [`ADD --unpack` flag](https://docs.docker.com/reference/dockerfile/#add---unpack) * [BuildKit overview](https://docs.docker.com/build/buildkit/) # tally/prefer-canonical-stopsignal Source: https://tally.wharflab.com/rules/tally/prefer-canonical-stopsignal STOPSIGNAL should use canonical signal names for clarity and consistency. STOPSIGNAL should use canonical signal names for clarity and consistency. | Property | Value | | -------- | ---------- | | Severity | Info | | Category | Style | | Default | Enabled | | Auto-fix | Yes (safe) | ## Description `STOPSIGNAL` accepts signal names in many formats: numeric values (`9`, `15`), names without the `SIG` prefix (`TERM`, `QUIT`), quoted strings (`"SIGINT"`), mixed case (`sigterm`), and non-standard real-time signal names (`RTMIN+3`). While Docker accepts all of these, canonical signal names are easier to read and easier to connect to upstream daemon documentation. This rule suggests the canonical form: * Ordinary signals: `SIGTERM`, `SIGINT`, `SIGQUIT`, `SIGKILL`, etc. * Real-time signals: `SIGRTMIN+3` Environment variable references (e.g. `STOPSIGNAL $MY_SIGNAL`) are skipped because the signal value cannot be determined statically. Windows stages are skipped because `STOPSIGNAL` has no effect on Windows containers — POSIX signals are not delivered to Windows processes. ## References * [Dockerfile reference -- STOPSIGNAL](https://docs.docker.com/reference/dockerfile/#stopsignal) * [signal(7) -- Linux manual page](https://man7.org/linux/man-pages/man7/signal.7.html) ## Examples ### Bad ```dockerfile theme={null} FROM alpine:3.20 # Numeric signal value STOPSIGNAL 15 CMD ["/app"] ``` ```dockerfile theme={null} FROM nginx:1.27 # Missing SIG prefix STOPSIGNAL QUIT CMD ["nginx", "-g", "daemon off;"] ``` ```dockerfile theme={null} FROM postgres:16 # Quoted signal name STOPSIGNAL "SIGINT" CMD ["postgres"] ``` ```dockerfile theme={null} FROM fedora:40 # Non-canonical real-time signal STOPSIGNAL RTMIN+3 CMD ["/sbin/init"] ``` ### Good ```dockerfile theme={null} FROM alpine:3.20 STOPSIGNAL SIGTERM CMD ["/app"] ``` ```dockerfile theme={null} FROM nginx:1.27 STOPSIGNAL SIGQUIT CMD ["nginx", "-g", "daemon off;"] ``` ```dockerfile theme={null} FROM postgres:16 STOPSIGNAL SIGINT CMD ["postgres"] ``` ```dockerfile theme={null} FROM fedora:40 STOPSIGNAL SIGRTMIN+3 CMD ["/sbin/init"] ``` ## Auto-fix The fix replaces the non-canonical signal token with its canonical form: ```bash theme={null} tally lint --fix Dockerfile ``` The fix uses `FixSafe` safety because the canonical form is semantically identical to the original — Docker normalizes signal names internally, so no runtime behavior changes. ## Configuration ```toml theme={null} [rules.tally.prefer-canonical-stopsignal] severity = "info" # Options: "off", "error", "warning", "info", "style" ``` # tally/prefer-copy-chmod Source: https://tally.wharflab.com/rules/tally/prefer-copy-chmod Prefer `COPY --chmod` over a separate `COPY` followed by `RUN chmod`. Prefer `COPY --chmod` over a separate `COPY` followed by `RUN chmod`. | Property | Value | | -------- | ------------- | | Severity | Info | | Category | Style | | Default | Enabled | | Auto-fix | Yes (`--fix`) | ## Description Detects a `COPY` instruction immediately followed by a `RUN chmod` that targets the same file, and suggests merging them into a single `COPY --chmod` instruction. The [`--chmod` flag](https://docs.docker.com/reference/dockerfile/#copy---chmod) sets file permissions at copy time, eliminating an extra layer and the overhead of running a shell container just to change permissions. ## Why use COPY --chmod? * **Fewer layers**: Merging two instructions into one reduces image layer count * **Performance**: `COPY --chmod` sets permissions without spawning a shell container * **Readability**: A single instruction is cleaner and easier to understand ## Detected Patterns The rule flags consecutive `COPY` + `RUN chmod` pairs where: 1. The COPY has a single source file, heredoc, or single-dest content (not a glob or multiple sources) 2. The `RUN` is a standalone `chmod` command (shell-form or exec-form, not chained with other commands) 3. The chmod target matches the COPY effective destination (resolved against `WORKDIR` for relative paths) Both octal (`755`, `0755`) and symbolic (`+x`, `u+rwx`, `-x`) chmod modes are supported. ### Merging with existing `--chmod` When the COPY already has `--chmod`, the rule still fires if a `RUN chmod` follows: * **Symbolic overlay**: `COPY --chmod=644` + `RUN chmod +x` merges to `COPY --chmod=0755` * **Octal override**: `COPY --chmod=644` + `RUN chmod 755` merges to `COPY --chmod=755` * **Redundant chmod**: `COPY --chmod=777` + `RUN chmod +x` flags the useless RUN (777 already includes execute) ## Examples ### Before (violation) ```dockerfile theme={null} FROM python:3.12-slim COPY entrypoint.sh /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh COPY --chown=appuser:appuser start.sh /usr/local/bin/start.sh RUN chmod 755 /usr/local/bin/start.sh COPY --chmod=644 config.sh /app/config.sh RUN chmod +x /app/config.sh ``` ### After (fixed with --fix) ```dockerfile theme={null} FROM python:3.12-slim COPY --chmod=+x entrypoint.sh /app/entrypoint.sh COPY --chmod=755 --chown=appuser:appuser start.sh /usr/local/bin/start.sh COPY --chmod=0755 config.sh /app/config.sh ``` ## Auto-fix Conditions The fix is emitted when: * The COPY has a single source file or heredoc content * The `RUN` is a standalone chmod (single command, not recursive, shell-form or exec-form) * The chmod target matches the COPY destination (absolute path or resolved via `WORKDIR`) The fix preserves the original chmod notation (symbolic or octal) in the `--chmod` flag value. When merging with an existing `--chmod`, the result is formatted as octal. ## Cross-Rule Interactions * **`tally/prefer-copy-heredoc`**: Converts `RUN echo > file` to `COPY < file` patterns with `COPY <