Skip to main content

Auto-harden any docker-compose.yml (cap_drop, no-new-privileges, read_only, tmpfs) and generate a human-readable audit report explaining every change.

Project description

compose-harden

CI License: MIT

Auto-harden any docker-compose.yml and get a document explaining every change it made — and every issue it noticed but refused to guess at.

$ compose-harden docker-compose.yml --dry-run
   depends_on:
     - db
+    cap_drop:
+      - ALL
+    security_opt:
+      - no-new-privileges:true
+    read_only: true
+    tmpfs:
+      - /tmp

...plus an AUDIT.md that explains why each line was added, in plain language, service by service.

Don't want to run it just to see what it does? A real, generated example is checked into this repo: examples/docker-compose.yml (input) → examples/sample-output/docker-compose.hardened.yml

Why this exists

There are great tools for scanning a Docker setup for problems (docker-bench-security, Trivy, CrowdSec, Docker's own Hardened Images at the base-image level). There's nothing that closes the loop end to end:

  1. read an existing, real-world docker-compose.yml
  2. apply the standard container-hardening controls automatically, without breaking the file's comments or formatting
  3. explain, in a document a non-security person can read, what changed and why — and just as importantly, what it refused to touch automatically and why

compose-harden does exactly that one job.

What it changes

Control What it does Why
cap_drop: [ALL] Drops every Linux capability Shrinks what a compromised process can do to almost nothing
security_opt: [no-new-privileges:true] Blocks privilege escalation via setuid binaries Closes a whole class of local priv-esc
read_only: true Makes the root filesystem read-only A code-exec bug can't persist anything on disk
tmpfs: [/tmp, ...] Adds in-memory writable scratch space Keeps the app working under read_only

Full rationale for each is in compose_harden/rules.py and gets reproduced in the generated AUDIT.md for the exact services it touched.

What it deliberately does not auto-fix

compose-harden will detect and report these, but won't silently change them, because a wrong guess here can break your service outright:

  • privileged: true — sometimes genuinely required; needs a human to say why.
  • Unpinned images (:latest or no tag) — this tool doesn't know which pinned version/digest is safe for your registry.
  • No non-root user: — the correct UID is image-specific; guessing wrong breaks filesystem permissions on startup.

Every flagged item shows up in the audit report with the reasoning, so you can decide, not the tool.

Design principle: never re-serialize your YAML

Most tools that "fix" a YAML file parse it into a data structure, edit the structure, and dump it back out — which silently strips comments, reorders keys, and changes quoting style. compose-harden never does this. It reads your file as plain text, decides what to change using a read-only parse, and then makes surgical line insertions at the correct indentation. Everything you didn't ask it to touch comes out byte-for-byte identical.

Install

pip install compose-harden   # once published
# or, from source:
git clone https://github.com/joshua-michael/compose-harden
cd compose-harden
pip install -e .

Usage

# Show what would change + full audit report, write nothing
compose-harden docker-compose.yml --dry-run

# Write docker-compose.hardened.yml + AUDIT.md next to it
compose-harden docker-compose.yml

# Overwrite in place (keeps a .bak backup automatically)
compose-harden docker-compose.yml --in-place

# Skip a rule you don't want applied
compose-harden docker-compose.yml --skip read_only --skip tmpfs

# Add extra writable paths some images need alongside read_only
compose-harden docker-compose.yml --extra-tmpfs /var/cache/nginx --extra-tmpfs /var/run

# Custom output locations
compose-harden docker-compose.yml -o hardened.yml --audit-output SECURITY-CHANGES.md

Run compose-harden --help for the full flag list.

How it decides what's safe to touch

  • If a control is already present and correct → skipped, marked "already present" in the audit (the tool is idempotent — running it twice is a no-op the second time).
  • If a control is present but customized (e.g. you already listed specific capabilities in cap_drop instead of ALL) → left untouched and flagged as a conflict for manual review, instead of overwriting your deliberate choice.
  • If a control is absent → added, using the same indentation style as the rest of your file.
  • tmpfs is only added when read_only is actually active for that service — there's no point adding writable scratch space you don't need.

Limitations

  • Only edits cap_drop, security_opt (no-new-privileges), read_only, and tmpfs. It does not touch networking, port bindings, secrets, resource limits, or healthchecks — that's a deliberate scope boundary, not an oversight.
  • Assumes conventional 2-space YAML indentation and a top-level services: map (the vast majority of real-world compose files). YAML anchors/aliases and extends: are not specially handled yet.
  • This is a fast first pass, not a substitute for an actual security review or for Docker's own Hardened Images at the base-image layer — use both.

Development

pip install -e ".[dev]"
pytest

License

MIT — see LICENSE.

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_harden-0.1.0.tar.gz (17.5 kB view details)

Uploaded Source

Built Distribution

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

compose_harden-0.1.0-py3-none-any.whl (16.0 kB view details)

Uploaded Python 3

File details

Details for the file compose_harden-0.1.0.tar.gz.

File metadata

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

File hashes

Hashes for compose_harden-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2e90dde0e5c21b21d558dcbd1ec5c1daf582f52ac6760e1d70546453425a79dd
MD5 0158d7f6a47babadb5d147e2f729f160
BLAKE2b-256 5010b41398b455a8579ee8d63fae053573e10625c3d15e342300a90d04685516

See more details on using hashes here.

Provenance

The following attestation bundles were made for compose_harden-0.1.0.tar.gz:

Publisher: publish.yml on joshua-michael/compose-harden

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_harden-0.1.0-py3-none-any.whl.

File metadata

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

File hashes

Hashes for compose_harden-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 690e01abde531877874a2c343fdde29354c78a55f743da26ddae773f065f8c2d
MD5 3fa6fdcb9377830b6366ba1af12b82ca
BLAKE2b-256 ea90684023064f55ab6b822d036116ffa6038b643b185b9c15b43b59cfa74a0d

See more details on using hashes here.

Provenance

The following attestation bundles were made for compose_harden-0.1.0-py3-none-any.whl:

Publisher: publish.yml on joshua-michael/compose-harden

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