Skip to main content

A security-focused linter for Docker Compose files

Project description

                                                 __ _       __
  _________  ____ ___  ____  ____  ________     / /(_)___  / /_
 / ___/ __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \   / // / __ \/ __/
/ /__/ /_/ / / / / / / /_/ / /_/ (__  )  __/  / // / / / / /_
\___/\____/_/ /_/ /_/ .___/\____/____/\___/  /_//_/_/ /_/\__/
                   /_/

CI PyPI Python License

A security-focused linter for Docker Compose files. Catches dangerous misconfigurations before they reach production.

compose-lint targets the same niche Hadolint occupies for Dockerfiles: zero-config, opinionated, fast, and grounded in OWASP and CIS standards.

Quick Start

pip install compose-lint
compose-lint

When run without arguments, compose-lint automatically finds compose.yml, compose.yaml, docker-compose.yml, or docker-compose.yaml in the current directory. You can also pass files explicitly:

compose-lint docker-compose.yml docker-compose.prod.yml

Docker

docker run --rm -v "$(pwd):/src" composelint/compose-lint

Or scan a specific file:

docker run --rm -v "$(pwd):/src" composelint/compose-lint docker-compose.prod.yml

Example Output

docker-compose.yml:5  CRITICAL  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
  fix: Use a Docker socket proxy (e.g., tecnativa/docker-socket-proxy)
       to expose only the API endpoints your service needs.
  ref: https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-1

docker-compose.yml:3  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: web
  fix: Bind to localhost: 127.0.0.1:8080:80
  ref: https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-5a

docker-compose.yml: 1 critical, 1 high

Rules

ID Severity Description OWASP CIS
CL-0001 CRITICAL Docker socket mounted Rule #1 5.31
CL-0002 CRITICAL Privileged mode enabled Rule #3 5.4
CL-0003 MEDIUM Privilege escalation not blocked Rule #4 5.25
CL-0004 MEDIUM Image not pinned to version Rule #13 5.27
CL-0005 HIGH Ports bound to all interfaces Rule #5a 5.13
CL-0006 MEDIUM No capability restrictions Rule #3 5.3
CL-0007 MEDIUM Filesystem not read-only Rule #8 5.12
CL-0008 HIGH Host network mode Rule #5 5.9
CL-0009 HIGH Security profile disabled Rule #6 5.21
CL-0010 HIGH Host namespace sharing Rule #3 5.8, 5.15, 5.16, 5.21
CL-0011 HIGH Dangerous capabilities added Rule #3 5.5
CL-0012 MEDIUM PIDs cgroup limit disabled 5.29
CL-0013 HIGH Sensitive host path mounted Rule #8 5.5
CL-0014 MEDIUM Logging driver disabled 5.x
CL-0015 LOW Healthcheck disabled 4.6, 5.27
CL-0016 HIGH Dangerous host device exposed 5.18
CL-0017 MEDIUM Shared mount propagation 5.20
CL-0018 MEDIUM Explicit root user Rule #7 5.x
CL-0019 MEDIUM Image tag without digest Rule #13 5.27

Severity Levels

Findings are rated LOW, MEDIUM, HIGH, or CRITICAL based on exploitability and impact scope. See docs/severity.md for the full scoring matrix.

Defaults are opinionated. Override any rule's severity in .compose-lint.yml if they don't match your environment.

Configuration

Create a .compose-lint.yml to disable rules or adjust severity:

rules:
  CL-0001:
    enabled: false          # Disable a rule
  CL-0003:
    enabled: false
    reason: "SEC-1234  Approved by J. Smith, expires 2026-07-01"
  CL-0005:
    severity: medium        # Downgrade to medium

Disabled rules still run — their findings appear as SUPPRESSED in the output without affecting the exit code. This gives reviewers and auditors visibility into what's being intentionally skipped.

The optional reason field records why a rule was disabled (e.g., an exception ticket number). It appears in all output formats:

  • Text: shown after the SUPPRESSED label
  • JSON: suppression_reason field
  • SARIF: native suppressions[].justification (recognized by GitHub Code Scanning)

To hide suppressed findings entirely:

compose-lint --skip-suppressed docker-compose.yml
compose-lint --config .compose-lint.yml docker-compose.yml

CLI Options

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 .compose-lint.yml config file
  --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. This means medium and low findings do not cause a non-zero exit — you can adopt compose-lint gradually without blocking CI on every finding immediately. To fail on all findings:

compose-lint --fail-on low docker-compose.yml

To only fail on critical issues (container escape, host compromise):

compose-lint --fail-on critical docker-compose.yml

CI Integration

GitHub Action

# .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@main
        with:
          sarif-file: results.sarif

This runs compose-lint and uploads findings to GitHub Code Scanning, where they appear as annotations on pull requests.

Manual setup

# .github/workflows/lint.yml
name: Compose Lint
on: [push, pull_request]

jobs:
  compose-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-python@v6
        with:
          python-version: "3.13"
      - run: pip install compose-lint
      - run: compose-lint docker-compose.yml

SARIF output for Code Scanning

compose-lint --format sarif docker-compose.yml > results.sarif

Pre-commit

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/tmatens/compose-lint
    rev: v0.2.0
    hooks:
      - id: compose-lint

Why not KICS/Checkov?

Those are excellent tools for full infrastructure scanning across Terraform, Kubernetes, Dockerfiles, and more. compose-lint solves a narrower problem:

  • Zero config: pip install && compose-lint file.yml. No policies to write, no plugins to configure.
  • Compose-specific: Every rule is designed for Docker Compose semantics, not adapted from a generic policy engine.
  • Actionable output: Every finding includes specific fix guidance and a direct link to the OWASP/CIS reference.
  • Fast: Sub-second for any compose file. No container runtime needed.

If you're already using KICS or Checkov and happy with the coverage, you don't need this. If you want a lightweight, focused tool for Compose files specifically, this is it.

Contributing

See CONTRIBUTING.md for development setup and how to add rules.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

compose_lint-0.3.3.tar.gz (75.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

compose_lint-0.3.3-py3-none-any.whl (41.6 kB view details)

Uploaded Python 3

File details

Details for the file compose_lint-0.3.3.tar.gz.

File metadata

  • Download URL: compose_lint-0.3.3.tar.gz
  • Upload date:
  • Size: 75.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for compose_lint-0.3.3.tar.gz
Algorithm Hash digest
SHA256 3a3f5f47bc4b0d97acad98b4eedb5e6077ac2f6d5e9e6f243a78c57f7d8e1175
MD5 89e1c3de834da454a140a60487d5980a
BLAKE2b-256 754ae16908ed59ea1e0ff0490a9eee5f25d6b93516bedd7444d984fbb43aa418

See more details on using hashes here.

Provenance

The following attestation bundles were made for compose_lint-0.3.3.tar.gz:

Publisher: publish.yml on tmatens/compose-lint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file compose_lint-0.3.3-py3-none-any.whl.

File metadata

  • Download URL: compose_lint-0.3.3-py3-none-any.whl
  • Upload date:
  • Size: 41.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for compose_lint-0.3.3-py3-none-any.whl
Algorithm Hash digest
SHA256 24775826034a03528646b7149b4aaf2521ce98b06ee4d4688bb427f7bdc2eb15
MD5 034bf48cae7342bf8e655a2d1e7fbf1d
BLAKE2b-256 1a7baed2e3d8e8909086a359f9e92de2a1a7e96227462eaefd7c836e0ffe1e6b

See more details on using hashes here.

Provenance

The following attestation bundles were made for compose_lint-0.3.3-py3-none-any.whl:

Publisher: publish.yml on tmatens/compose-lint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page