OTOthoTools

Docker Compose Validator

Catch container security risks
Input
Result5 findings
  • !privileged: true grants almost host-level access.
  • !A password appears inline. Use secrets or an external secret store.
  • Pin the image to an immutable version or digest instead of :latest.
  • Published port appears exposed on all interfaces; bind to 127.0.0.1 when possible.
  • Consider read_only: true when the root filesystem need not be writable.

Introduction

The Docker Compose Validator reviews a pasted compose.yaml for five container-security risks that show up in production incidents: privileged mode, secrets in plaintext, unpinned images, ports bound to all interfaces, and a writable root filesystem.

Compose files read like configuration, but they become the security boundary of your containers. Small mistakes here map directly to host-level risk.

Objective

  • Flag privileged: true, which is close to host-level access.
  • Detect passwords and credentials written inline in the file.
  • Warn about :latest image tags, which are not reproducible.
  • Warn about published ports exposed on all interfaces and about the absence of read_only: true.

Inputs

  • A paste of compose.yaml (or the services section of it).
  • YAML structure is not parsed deeply; checks are pattern-based on the visible text.

How it works

The file is scanned with five pattern checks. privileged: true is a high finding because it grants almost every capability and disables most isolation.

Any line containing a password assignment (password: or PASSWORD=) is a high finding: secrets belong in Docker secrets or an external secret store.

image: ...:latest produces a warning because the same file can pull different content over time; an immutable digest or a pinned version is reproducible.

A published port (host:container) not bound to 127.0.0.1 is a warning, as is a service without read_only: true on its root filesystem.

Testable example

Try it — the analysis runs locally in your browser.

Example

services:
  api:
    image: example/api:latest
    ports:
      - "8080:8080"
    environment:
      - DB_PASSWORD=change-me
    privileged: true

Expected output

High: privileged: true grants almost host-level access.
High: A password appears inline. Use secrets or an external secret store.
Warning: Pin the image to an immutable version or digest instead of :latest.
Warning: Published port appears exposed on all interfaces.
Warning: Consider read_only: true when the root filesystem need not be writable.

Reading the output

  • Remove privileged mode whenever possible; most workloads need only specific capabilities.
  • Move inline secrets to Docker secrets or an external vault, and rotate anything that was committed.
  • Pinned tags or digests make deployments reproducible and rollbacks predictable.
  • Binding ports to 127.0.0.1 keeps services private unless a proxy deliberately exposes them.

Risks

  • privileged: true on a compromised container is effectively host compromise.
  • Inline passwords leak into images, registries and logs and are never rotated cleanly.
  • :latest breaks rollback: yesterday's working image may be overwritten by today's broken build.

Limitations

  • Pattern checks can miss risks expressed with YAML anchors, environment files or Dockerfile instructions.
  • It does not run the Compose parser, so YAML syntax errors are not reported here.
  • It cannot detect vulnerabilities in the images themselves.

Official references

FAQ

When is privileged: true justified?

Almost never in production. Some legacy drivers and specific device access need it, but the safe path is granting the exact capabilities required and using devices: for hardware access.

What should I use instead of inline passwords?

Docker secrets for swarm, environment files with restricted permissions and external references for vault-style secrets. The key is that secrets never sit in the compose file or image.

Does read_only: true break apps that write files?

Only if they write to the root filesystem. Writable paths (uploads, caches) can be mounted as volumes or declared with tmpfs, keeping the rest read-only.