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

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

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.2.0.tar.gz (44.1 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.2.0-py3-none-any.whl (29.6 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for compose_lint-0.2.0.tar.gz
Algorithm Hash digest
SHA256 421824dfaa5dc3df7c47448f2cad5c8360fdc16294be595a66d690c5bf52187c
MD5 171ebdb8af95141198ce1e36051a14ed
BLAKE2b-256 2e44727dfb92ccd7291ecbb5f8e09d399e1f56b9eafae64f0ce9b7dd9e9d05ee

See more details on using hashes here.

Provenance

The following attestation bundles were made for compose_lint-0.2.0.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.2.0-py3-none-any.whl.

File metadata

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

File hashes

Hashes for compose_lint-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2176adb0fa1b0991d096047e9e2fb7ee821eafbd6ff6f9d88301003b7c8c89b8
MD5 23ee9118fb1929e17879291f0c700666
BLAKE2b-256 0e9b39711983c5aa1b3ade7433fd769141816fd79c4e3b9e11f087615b25d935

See more details on using hashes here.

Provenance

The following attestation bundles were made for compose_lint-0.2.0-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