https://github.com/tmatens/compose-lint
Security-focused linter for Docker Compose files. Catches dangerous misconfigurations before they reach production. Grounded in OWASP and CIS Docker Benchmark.
https://github.com/tmatens/compose-lint
cis-benchmark code-quality compose container-security devops devsecops docker docker-compose github-actions hardening iac-security linter owasp pre-commit python security security-scanner security-tools static-analysis yaml
Last synced: about 2 months ago
JSON representation
Security-focused linter for Docker Compose files. Catches dangerous misconfigurations before they reach production. Grounded in OWASP and CIS Docker Benchmark.
- Host: GitHub
- URL: https://github.com/tmatens/compose-lint
- Owner: tmatens
- License: mit
- Created: 2026-04-06T03:45:55.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-04-18T05:47:38.000Z (2 months ago)
- Last Synced: 2026-04-18T06:27:47.811Z (2 months ago)
- Topics: cis-benchmark, code-quality, compose, container-security, devops, devsecops, docker, docker-compose, github-actions, hardening, iac-security, linter, owasp, pre-commit, python, security, security-scanner, security-tools, static-analysis, yaml
- Language: Python
- Size: 305 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Codeowners: .github/CODEOWNERS
- Security: SECURITY.md
- Roadmap: docs/ROADMAP.md
- Agents: AGENTS.md
Awesome Lists containing this project
README
# compose-lint
[](https://github.com/tmatens/compose-lint/actions/workflows/ci.yml)
[](https://pypi.org/project/compose-lint/)
[](https://hub.docker.com/r/composelint/compose-lint)
[](https://pypi.org/project/compose-lint/)
[](https://github.com/tmatens/compose-lint/blob/main/LICENSE)
[](https://scorecard.dev/viewer/?uri=github.com/tmatens/compose-lint)
[](https://www.bestpractices.dev/projects/12472)
[](https://www.bestpractices.dev/projects/12472)
A security-focused linter for Docker Compose files. Catches dangerous misconfigurations before they reach production.
compose-lint targets the same niche [Hadolint](https://github.com/hadolint/hadolint) occupies for Dockerfiles: zero-config, opinionated, fast, and grounded in [OWASP](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html) and [CIS](https://www.cisecurity.org/benchmark/docker) standards.
## Installation
**pip**
```bash
pip install compose-lint
```
**Docker** — [composelint/compose-lint](https://hub.docker.com/r/composelint/compose-lint)
```bash
docker run --rm -v "$(pwd):/src" composelint/compose-lint
```
The image runs on [Google's distroless Python](https://github.com/GoogleContainerTools/distroless) (Debian 13, Python 3.13): no shell, no package manager, no `apt`, no `pip` at runtime. The entrypoint runs as nonroot (UID 65532). Only the Python interpreter, PyYAML, and `compose_lint` itself live in the final image — pip and the `dist-info` metadata are stripped from the venv after the build stage so Python-ecosystem CVEs don't surface on an unreachable binary. Multi-arch (linux/amd64 + linux/arm64), SHA-pinned base images bumped by Renovate, SLSA build provenance and Sigstore attestations published with every release. See [ADR-009](https://github.com/tmatens/compose-lint/blob/main/docs/adr/009-runtime-base-image.md).
## Quick Start
Run without arguments to auto-detect `compose.yml`, `compose.yaml`, `docker-compose.yml`, or `docker-compose.yaml` in the current directory:
```bash
compose-lint
```
Or pass files explicitly:
```bash
compose-lint docker-compose.yml docker-compose.prod.yml
```
Docker equivalent:
```bash
docker run --rm -v "$(pwd):/src" composelint/compose-lint docker-compose.prod.yml
```
## Example Output
Given this `docker-compose.yml`:
```yaml
services:
traefik:
image: traefik:v3.0@sha256:aaaabbbbccccddddeeeeffff00001111222233334444555566667777888899990
read_only: true
cap_drop: [ALL]
security_opt:
- no-new-privileges:true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- "8080:80"
```
and this `.compose-lint.yml` (suppressing CL-0001 for `traefik` with a tracked reason):
```yaml
rules:
CL-0001:
exclude_services:
traefik: "SEC-1234 approved — socket proxy planned for 2026-Q3"
```
running `compose-lint docker-compose.yml` produces:
```
compose-lint 0.3.7
files: docker-compose.yml · config: .compose-lint.yml · fail-on: high
docker-compose.yml:8 SUPPRESSED CL-0001 Docker socket mounted via '/var/run/docker.sock:/var/run/docker.sock'. This gives the container full control over the Docker daemon.
service: traefik
reason: SEC-1234 approved — socket proxy planned for 2026-Q3
docker-compose.yml:10 HIGH CL-0005 Port '8080:80' is bound to all interfaces. Docker bypasses host firewalls (UFW/firewalld), potentially exposing this port to the public internet.
service: traefik
fix: Bind to localhost: 127.0.0.1:8080:80
If public access is needed, use a reverse proxy with TLS.
ref: https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-5a---be-careful-when-mapping-container-ports-to-the-host-with-firewalls-like-ufw
docker-compose.yml: 1 high · 1 suppressed (not counted)
✗ FAIL · 1 finding at or above high
```
Exit code is `1` (one finding at or above the default `--fail-on high` threshold). Suppressed findings are shown for auditability but do not count toward the threshold.
## Rules
| ID | Severity | Description | OWASP | CIS |
|----|----------|-------------|-------|-----|
| [CL-0001](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0001.md) | CRITICAL | Docker socket mounted | [Rule #1](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-1---do-not-expose-the-docker-daemon-socket-even-to-the-containers) | 5.31 |
| [CL-0002](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0002.md) | CRITICAL | Privileged mode enabled | [Rule #3](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-3---do-not-run-containers-with-the---privileged-flag) | 5.4 |
| [CL-0003](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0003.md) | MEDIUM | Privilege escalation not blocked | [Rule #4](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-4---add-no-new-privileges-flag) | 5.25 |
| [CL-0004](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0004.md) | MEDIUM | Image not pinned to version | [Rule #13](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-13---enhance-supply-chain-security) | 5.27 |
| [CL-0005](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0005.md) | HIGH | Ports bound to all interfaces | [Rule #5a](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-5a---be-careful-when-mapping-container-ports-to-the-host-with-firewalls-like-ufw) | 5.13 |
| [CL-0006](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0006.md) | MEDIUM | No capability restrictions | [Rule #3](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-3---limit-capabilities-grant-only-specific-capabilities-needed-by-a-container) | 5.3 |
| [CL-0007](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0007.md) | MEDIUM | Filesystem not read-only | [Rule #8](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-8---set-filesystem-and-volumes-to-read-only) | 5.12 |
| [CL-0008](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0008.md) | HIGH | Host network mode | [Rule #5](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-5---be-mindful-of-inter-container-connectivity) | 5.9 |
| [CL-0009](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0009.md) | HIGH | Security profile disabled | [Rule #6](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-6---use-linux-security-module-seccomp-apparmor-or-selinux) | 5.21 |
| [CL-0010](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0010.md) | HIGH | Host namespace sharing | [Rule #3](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-3---limit-capabilities-grant-only-specific-capabilities-needed-by-a-container) | 5.8, 5.15, 5.16, 5.21 |
| [CL-0011](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0011.md) | HIGH | Dangerous capabilities added | [Rule #3](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-3---limit-capabilities-grant-only-specific-capabilities-needed-by-a-container) | 5.5 |
| [CL-0012](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0012.md) | MEDIUM | PIDs cgroup limit disabled | — | 5.29 |
| [CL-0013](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0013.md) | HIGH | Sensitive host path mounted | [Rule #8](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-8---set-filesystem-and-volumes-to-read-only) | 5.5 |
| [CL-0014](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0014.md) | MEDIUM | Logging driver disabled | — | 5.x |
| [CL-0015](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0015.md) | LOW | Healthcheck disabled | — | 4.6, 5.27 |
| [CL-0016](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0016.md) | HIGH | Dangerous host device exposed | — | 5.18 |
| [CL-0017](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0017.md) | MEDIUM | Shared mount propagation | — | 5.20 |
| [CL-0018](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0018.md) | MEDIUM | Explicit root user | [Rule #7](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-7---do-not-run-containers-with-a-root-user) | 5.x |
| [CL-0019](https://github.com/tmatens/compose-lint/blob/main/docs/rules/CL-0019.md) | MEDIUM | Image tag without digest | [Rule #13](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-13---enhance-supply-chain-security) | 5.27 |
## Severity Levels
Findings are rated **LOW**, **MEDIUM**, **HIGH**, or **CRITICAL** based on exploitability and impact scope. See [docs/severity.md](https://github.com/tmatens/compose-lint/blob/main/docs/severity.md) for the full scoring matrix.
## Configuration
Create `.compose-lint.yml` to disable rules or adjust severity:
```yaml
rules:
CL-0001:
enabled: false
CL-0003:
enabled: false
reason: "SEC-1234 — Approved by J. Smith, expires 2026-07-01"
CL-0005:
severity: medium
```
Disabled rules still run — findings appear as **SUPPRESSED** without affecting the exit code. The `reason` field is surfaced in all output formats:
- **Text**: shown after the `SUPPRESSED` label
- **JSON**: `suppression_reason` field
- **SARIF**: `suppressions[].justification` (recognized by GitHub Code Scanning)
To hide suppressed findings from output:
```bash
compose-lint --skip-suppressed docker-compose.yml
```
### Per-service rule exclusions
When a rule is valid for some services but architecturally incompatible with
others (e.g. CL-0003 `no-new-privileges` and an image whose entrypoint
switches users), use `exclude_services` to suppress it for just the
affected services while keeping it active elsewhere:
```yaml
rules:
CL-0003:
exclude_services:
minecraft: "entrypoint switches users via su-exec"
backup: "forks as different user"
CL-0007:
exclude_services:
- legacy-worker # list form when no reason is needed
```
Excluded services still produce findings marked **SUPPRESSED** with the
per-service reason flowing to `suppression_reason` / SARIF `justification`,
same as a global disable. Service names are matched exactly; unknown names
produce a stderr warning but do not error (Compose files are edited
independently of config). Global `enabled: false` takes precedence over
per-service exclusions.
## CLI Reference
```
compose-lint [OPTIONS] [FILE ...]
--format {text,json,sarif} Output format (default: text)
--fail-on SEVERITY Minimum severity to trigger exit 1 (default: high)
--skip-suppressed Hide suppressed findings from output
--config PATH Path to config file (default: .compose-lint.yml)
--explain CL-XXXX Print the full documentation for a single rule
--version Show version and exit
```
## Exit Codes
| Code | Meaning |
|------|---------|
| 0 | No findings at or above the `--fail-on` threshold |
| 1 | One or more findings at or above the `--fail-on` threshold |
| 2 | Usage error (invalid args, file not found, invalid Compose file) |
The default threshold is `high` — medium and low findings don't fail CI unless you opt in:
```bash
compose-lint --fail-on low docker-compose.yml # fail on everything
compose-lint --fail-on critical docker-compose.yml # only critical
```
## CI Integration
### GitHub Actions
The easiest path — runs compose-lint and uploads findings to GitHub Code Scanning:
```yaml
# .github/workflows/lint.yml
name: Compose Lint
on: [push, pull_request]
jobs:
compose-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: tmatens/compose-lint@v0.3.7
with:
sarif-file: results.sarif
```
Or install from PyPI directly:
```yaml
- uses: actions/setup-python@v6
with:
python-version: "3.13"
- run: pip install compose-lint
- run: compose-lint docker-compose.yml
```
### SARIF output
```bash
compose-lint --format sarif docker-compose.yml > results.sarif
```
## Pre-commit
```yaml
# .pre-commit-config.yaml
repos:
- repo: https://github.com/tmatens/compose-lint
rev: v0.3.7
hooks:
- id: compose-lint
```
## How it compares
| Tool | Compose security rules | Scope | Zero config |
|------|----------------------|-------|-------------|
| **compose-lint** | Yes | Docker Compose | Yes |
| **KICS** | Yes | Broad IaC (Terraform, K8s, Compose, ...) | No |
| **Hadolint** | No — Dockerfile only | Dockerfile | Yes |
| **dclint** | Yes — schema/structure only | Docker Compose | Yes |
| **Trivy** | No — Dockerfile + image scanning | Dockerfiles, images, repos | Yes |
| **Checkov** | No — no Compose support | Broad IaC (Terraform, K8s, ...) | No |
If you need broad IaC coverage across Terraform, Kubernetes, and more, KICS covers Docker Compose and is worth evaluating. If you want a lightweight, focused tool with zero config and actionable fix guidance for Compose files specifically, this is it.
## Contributing
See [CONTRIBUTING.md](https://github.com/tmatens/compose-lint/blob/main/CONTRIBUTING.md) for development setup and how to add rules.
## License
[MIT](https://github.com/tmatens/compose-lint/blob/main/LICENSE)