BackendBench
Coffee
Doctor

Docker Compose doctor

Paste your docker-compose.yml and get a list of risks and mistakes: published database ports, privileged containers, mounted Docker sockets, floating image tags, missing healthchecks and references to services or volumes that do not exist.

Everything runs in your browser. Nothing you paste is uploaded. Also try the paste-anything box or the AI-output verifier.

What the Docker Compose doctor checks

A Compose file often starts as a local convenience and quietly becomes the production deployment. The defaults are generous: every published port listens on all interfaces, containers run as root, and depends_on only waits for a process to start. The rules here flag the choices that cause outages and break-ins, and the structural mistakes that Compose itself only reports when you try to start the stack.

There are 20 rules in this doctor: 8 high severity, 5 medium and 7 low. Each rule has a stable anchor, so you can link a teammate straight to the explanation, for example /doctor/docker-compose/#rule-compose-version-obsolete.

All 20 rules

LowThe version key is obsolete#

Why it matters. Compose v2 ignores the top-level version field and prints a warning. It also misleads readers about which features apply.

Fix. Delete the version line. Compose to Kubernetes

version: "3.8"
services:
  a:
    image: nginx:1.27

HighService has neither image nor build#

Why it matters. Compose cannot start a service without an image or a build context.

Fix. Add image: name:tag or a build: section. Compose to Kubernetes

services:
  a:
    ports: ["80:80"]

MediumImage uses latest or no tag#

Why it matters. An untagged image is :latest, so the same file pulls different software next month and a rollback cannot reproduce today.

Fix. Pin a version tag, or better a digest: postgres:16.4 or image@sha256:... Compose to Kubernetes

services:
  a:
    image: postgres

HighPrivileged container#

Why it matters. privileged: true gives the container nearly full access to the host, so a compromised app owns the machine.

Fix. Remove it and add only the specific capability or device you need.

services:
  a:
    image: x:1
    privileged: true

MediumHost networking#

Why it matters. network_mode: host removes network isolation and ignores ports: mappings.

Fix. Use a user-defined network and publish only the ports you need.

services:
  a:
    image: x:1
    network_mode: host

HighDocker socket mounted#

Why it matters. Mounting /var/run/docker.sock hands the container root on the host, because it can start any container.

Fix. Avoid it, or put a socket proxy with a narrow allow list in front.

services:
  a:
    image: x:1
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

MediumDatabase or broker port published to every interface#

Why it matters. A published port like 5432:5432 listens on 0.0.0.0, so anyone on the network (or the internet on a cloud VM) can reach the database.

Fix. Bind to loopback ("127.0.0.1:5432:5432") or drop ports: and let other services use the internal network. Ports cheat sheet

services:
  db:
    image: postgres:16
    ports:
      - "5432:5432"

MediumPassword written in the Compose file#

Why it matters. Literal secrets in the file end up in git and in docker inspect output.

Fix. Use ${DB_PASSWORD} from an .env file that is git-ignored, Docker secrets, or env_file.

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: hunter2

LowNo restart policy#

Why it matters. Without restart a crashed or host-rebooted container stays down.

Fix. Add restart: unless-stopped (or on-failure for batch jobs).

services:
  a:
    image: x:1

LowNo healthcheck#

Why it matters. Without a healthcheck, depends_on can only wait for the process to start, not for the database to accept connections.

Fix. Add a healthcheck (pg_isready, curl -f, redis-cli ping) and use depends_on with condition: service_healthy.

services:
  db:
    image: postgres:16

Lowdepends_on only waits for start#

Why it matters. The short list form starts the dependency first but does not wait until it is ready, a classic cause of "connection refused" at boot.

Fix. Use the long form with condition: service_healthy and a healthcheck on the dependency.

services:
  a:
    image: x:1
    depends_on:
      - db
  db:
    image: x:1

LowNo memory limit#

Why it matters. One leaking container can take the host down with the OOM killer picking victims at random.

Fix. Set deploy.resources.limits.memory (or mem_limit) and a JVM -XX:MaxRAMPercentage that fits. JVM memory calculator

services:
  a:
    image: x:1

LowFixed container_name#

Why it matters. A fixed name blocks scaling (--scale) and clashes if the project is started twice.

Fix. Remove container_name and let Compose name it project-service-1.

services:
  a:
    image: x:1
    container_name: mine

HighDangerous Linux capability added#

Why it matters. SYS_ADMIN, NET_ADMIN and ALL give the container host-level powers.

Fix. Add the narrowest capability you need, and cap_drop: [ALL] first.

services:
  a:
    image: x:1
    cap_add:
      - SYS_ADMIN

HighSame host port published twice#

Why it matters. Two services cannot bind the same host port; the second one fails with "port is already allocated".

Fix. Give each service a different host port.

services:
  a:
    image: x:1
    ports: ["8080:80"]
  b:
    image: y:1
    ports: ["8080:81"]

Highdepends_on names a service that does not exist#

Why it matters. Compose refuses to start with "depends on undefined service".

Fix. Fix the name, or define the service.

services:
  a:
    image: x:1
    depends_on:
      - ghost

HighNamed volume is not declared#

Why it matters. A named volume used by a service must be listed under top-level volumes, otherwise Compose stops with "refers to undefined volume".

Fix. Add it to the top-level volumes: section.

services:
  a:
    image: x:1
    volumes:
      - data:/var/lib/x

MediumContainer runs as root#

Why it matters. user: root (or 0) means a vulnerability in the app is a root shell in the container.

Fix. Run as an unprivileged user, for example user: "1000:1000".

services:
  a:
    image: x:1
    user: root

HighThe YAML does not parse#

Why it matters. Docker Compose stops with a yaml error and starts nothing.

Fix. Fix the indentation or quoting at the reported line. YAML to JSON

services:
  a:
    image: x
   b: 1

Frequently asked questions

Which Compose versions does it support?

The doctor reads the Compose Specification used by Docker Compose v2, including the long forms of ports, volumes and depends_on. The old top-level version key is reported as obsolete.

Why does it warn about published database ports?

A mapping such as 5432:5432 binds to 0.0.0.0, which exposes the database to the network. Bind to 127.0.0.1 or leave the port unpublished so only other services on the Compose network can reach it.

Will it check my .env file or variables?

Variables such as ${DB_PASSWORD} are treated as safe placeholders and are not resolved. Only literal secrets in the file are reported.

Can I convert the file to Kubernetes?

Yes. After fixing the findings, use the Compose to Kubernetes converter linked below to produce manifests.

More tools you might need