@contino/tally
v0.2.0
Published
A fast, configurable linter for Dockerfiles and Containerfiles
Maintainers
Readme
tally
A fast, configurable linter for Dockerfiles and Containerfiles.
Supported Rules
tally integrates rules from multiple sources:
| Source | Rules | Description | |--------|-------|-------------| | BuildKit | 22 rules | Docker's official Dockerfile checks (automatically captured) | | tally | 3 rules | Custom rules including secret detection with gitleaks | | Hadolint | 12 rules | Hadolint-compatible Dockerfile rules (expanding) |
See RULES.md for the complete rules reference.
Installation
Homebrew (macOS/Linux)
brew install tinovyatkin/tap/tallyNPM
npm install -g @contino/tallyPyPI
pip install tally-cliRubyGems
gem install tally-cliGo
go install github.com/tinovyatkin/tally@latestFrom Source
git clone https://github.com/tinovyatkin/tally.git
cd tally
go build .Usage
# Check a Dockerfile
tally check Dockerfile
# Check all Dockerfiles in current directory (recursive)
tally check .
# Check with glob patterns
tally check "**/*.Dockerfile"
# Exclude patterns
tally check --exclude "vendor/*" --exclude "test/*" .
# Check with max lines limit
tally check --max-lines 100 Dockerfile
# Output as JSON
tally check --format json Dockerfile
# Check multiple files
tally check Dockerfile.dev Dockerfile.prod
# Enable context-aware rules (e.g., copy-ignored-file)
tally check --context . DockerfileFile Discovery
When given a directory, tally recursively searches for Dockerfiles using these default patterns:
DockerfileDockerfile.*(e.g.,Dockerfile.dev,Dockerfile.prod)*.Dockerfile(e.g.,api.Dockerfile,frontend.Dockerfile)Containerfile(Podman convention)Containerfile.**.Containerfile
Use --exclude to filter out unwanted files:
# Exclude vendor and test directories
tally check --exclude "vendor/*" --exclude "test/*" .
# Exclude all .bak files
tally check --exclude "*.bak" .Rules Overview
For the complete list of all supported rules, see RULES.md.
Context-Aware Rules
Some rules require build context awareness. Enable them with the --context flag:
# Enable context-aware rules
tally check --context . Dockerfilecopy-ignored-file: Detects when COPY or ADD commands reference files that would be excluded by .dockerignore. This helps catch mistakes
where files are copied but won't actually be included in the build.
# .dockerignore contains: *.log
# This will trigger a warning:
COPY app.log /app/ # File matches .dockerignore pattern
# Heredoc sources are exempt (they're inline, not from context):
COPY <<EOF /app/config.txt
inline content
EOFIgnoring Violations
You can suppress specific violations using inline comment directives.
Next-Line Directives
Suppress violations on the next line:
# tally ignore=StageNameCasing
FROM alpine AS BuildGlobal Directives
Suppress violations throughout the entire file:
# tally global ignore=max-lines
FROM alpine
# ... rest of file is not checked for max-linesMultiple Rules
Suppress multiple rules with comma-separated values:
# tally ignore=StageNameCasing,DL3006
FROM Ubuntu AS BuildAdding Reasons
Document why a rule is being ignored using ;reason= (BuildKit-style separator):
# 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
FROM alpine
# check=skip=StageNameCasing;reason=Legacy naming convention
FROM alpine AS BuildUse --require-reason to enforce that all ignore directives include an explanation:
tally check --require-reason DockerfileNote: The ;reason= syntax is a tally extension that works with all directive formats. BuildKit silently ignores the reason option.
Migration Compatibility
tally supports directive formats from other linters for easy migration:
# hadolint ignore=DL3006
FROM ubuntu
# hadolint global ignore=DL3008
FROM alpine
# check=skip=StageNameCasing
FROM alpine AS BuildSuppressing All Rules
Use all to suppress all rules on a line:
# tally ignore=all
FROM Ubuntu AS BuildCLI Options
| 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 reason= explanation |
Configuration
Inline directive behavior can be configured in .tally.toml:
[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)Configuration
tally supports configuration via TOML config files, environment variables, and CLI flags.
Config File
Create a .tally.toml or tally.toml file in your project:
[output]
format = "text" # text, json, sarif, github-actions, markdown
path = "stdout" # stdout, stderr, or file path
show-source = true # Show source code snippets
fail-level = "style" # Minimum severity for exit code 1
# Rule selection (Ruff-style)
[rules]
include = ["buildkit/*", "tally/*"] # Enable rules by namespace or specific rule
exclude = ["buildkit/MaintainerDeprecated"] # Disable specific rules
# Per-rule configuration (severity, options)
[rules.tally.max-lines]
severity = "error"
max = 500
skip-blank-lines = true
skip-comments = true
[rules.buildkit.StageNameCasing]
severity = "info" # Downgrade severity
[rules.hadolint.DL3026]
severity = "warning"
trusted-registries = ["docker.io", "gcr.io"]Config File Discovery
tally uses cascading config discovery similar to Ruff:
- Starting from the Dockerfile's directory, walks up the filesystem
- Stops at the first
.tally.tomlortally.tomlfound - Uses that config (no merging with parent configs)
This allows monorepo setups with per-directory configurations.
Priority Order
Configuration sources are applied in this order (highest priority first):
- CLI flags (
--max-lines 100) - Environment variables (
TALLY_RULES_MAX_LINES_MAX=100) - Config file (
.tally.tomlortally.toml) - Built-in defaults
Environment Variables
| Variable | Description |
| ---------------------------------------- | --------------------------------------------------------- |
| TALLY_OUTPUT_FORMAT | Output format (text, json, sarif, github-actions, markdown) |
| 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) |
| TALLY_EXCLUDE | Glob pattern(s) to exclude files (comma-separated) |
| TALLY_CONTEXT | Build context directory for context-aware rules |
| 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 comments (true/false) |
| 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) |
CLI Flags
# Specify config file explicitly
tally check --config /path/to/.tally.toml Dockerfile
# Override max-lines from config
tally check --max-lines 200 Dockerfile
# Exclude blank lines and comments from count
tally check --max-lines 100 --skip-blank-lines --skip-comments DockerfileOutput Formats
tally supports multiple output formats for different use cases.
Text (default)
Human-readable output with colors and source code snippets:
tally check DockerfileWARNING: 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"
────────────────────JSON
Machine-readable format with summary statistics and scan metadata:
tally check --format json DockerfileThe JSON output includes:
files: Array of files with their violationssummary: Aggregate statistics (total, errors, warnings, etc.)files_scanned: Total number of files scannedrules_enabled: Number of active rules (withDefaultSeverity != "off")
{
"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": 35
}SARIF
Static Analysis Results Interchange Format for CI/CD integration with GitHub Code Scanning, Azure DevOps, and other tools:
tally check --format sarif Dockerfile > results.sarifGitHub Actions
Native GitHub Actions workflow command format for inline annotations:
tally check --format github-actions Dockerfile::warning file=Dockerfile,line=2,title=StageNameCasing::Stage name 'Builder' should be lowercaseMarkdown
Concise Markdown tables optimized for AI agents and token efficiency:
tally check --format markdown Dockerfile**2 issues** in `Dockerfile`
| Line | Issue |
|------|-------|
| 10 | ❌ Use absolute WORKDIR |
| 2 | ⚠️ Stage name 'Builder' should be lowercase |Features:
- Summary upfront with issue counts
- Sorted by severity (errors first)
- Emoji indicators: ❌ error, ⚠️ warning, ℹ️ info, 💅 style
- No rule codes or doc URLs (token-efficient)
- Multi-file support with File column when needed
Output Options
| 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 (also respects NO_COLOR env var) |
| --show-source | Show source code snippets (default: true) |
| --hide-source | Hide source code snippets |
Exit Codes
| Code | Meaning |
| ---- | ------------------------------------------------- |
| 0 | No violations (or below --fail-level threshold) |
| 1 | Violations found at or above --fail-level |
| 2 | Parse or configuration error |
Fail Level
Control which severity levels cause a non-zero exit code:
# Fail only on errors (ignore warnings)
tally check --fail-level error Dockerfile
# Never fail (useful for CI reporting without blocking)
tally check --fail-level none --format sarif Dockerfile > results.sarif
# Fail on any violation including style issues (default behavior)
tally check --fail-level style DockerfileAvailable levels (from most to least severe): error, warning, info, style (default), none
Development
Running Tests
# Run all tests
make test
# Run linting
make lint
# Run copy/paste detection (CPD)
make cpdCode Quality
This project uses:
- golangci-lint for Go linting
- PMD CPD for copy/paste detection (minimum 100 tokens)
Copy/paste detection runs automatically in CI and helps identify duplicate code patterns.
Contributing
See CLAUDE.md for development guidelines.
License
Apache-2.0
