Docker Compose Validator
Validate a Docker Compose file and catch the mistakes that only fail at `up` — a depends_on with no target, an undeclared volume, two services on one port.
compose.yaml
YAMLCompose project
4 services3 errors, 2 warnings
- Services
- 4
- Published ports
- 1
- Volumes
- 0
- Networks
- 0
- Secrets
- 0
- Profiles
- 0
Host ports this file would bind
8080/tcp
Issues
5 shown- errorHost port 8080/tcp is already published by service "api".
Only one container can bind a host port. The second to start fails with 'port is already allocated'.
/services/admin/ports/0
- error"cache" is not a service in this file.
Declared services: api, admin, db, redis.
/services/api/depends_on
- errorService "db" mounts named volume "pgdata", which is not declared at the top level.
Add it under the top-level `volumes:` key, or make it a bind mount by writing a path (./data:/var/lib).
/services/db/volumes/0
- warning`:latest` is not a version. What it resolves to changes without notice.
Pin a tag or a digest for anything you intend to reproduce.
/services/api/image
- warning`version` is obsolete. The Compose Spec dropped it and Docker Compose v2 ignores it.
Delete the line. It has no effect, and leaving it in makes the file look like a v2 schema file.
/version
Not checked
the limits of a local validator- Whether an image exists or can be pulled — no registry is contacted.
- Whether a build context, Dockerfile or env_file path exists — nothing on your disk is read.
- Whether a bind-mount source path exists on the host.
- Whether an environment variable referenced as ${VAR} is set in your shell or .env file.
- Whether the Docker Engine would accept a driver, capability or sysctl name.
Parsed and validated in your browser. A compose file usually holds credentials, so nothing here is uploaded — no registry is contacted and no path on your disk is read.
About the Docker Compose Validator
This Docker Compose validator checks a compose file the way the errors actually arrive: not just whether the YAML parses, but whether the parts refer to each other correctly. A depends_on pointing at a service you renamed, a named volume nobody declared, two services publishing the same host port — all three parse cleanly, survive docker compose config, and fail at docker compose up.
It runs entirely in your browser. That matters more here than for most tools, because a compose file is where database passwords, API keys and internal hostnames live. Nothing is uploaded, no registry is contacted, and no path on your disk is read.
Every finding is located by JSON Pointer — /services/api/depends_on rather than "line 14ish" — and separated into errors, which will stop the stack from coming up, and warnings, which are things worth knowing. The example loaded on the page is deliberately broken in three realistic ways so you can see what a real report looks like.
The scope is stated rather than implied. Any key outside the set this validator understands is listed as unchecked instead of quietly passed over, and the Not checked panel spells out the five things no local validator can know.
- Reference checks: depends_on, volumes, networks, secrets and configs must resolve to something declared
- Host port conflict detection across every service, including overlapping published ranges
- Dependency cycle detection, with the path through the cycle named
- depends_on: service_healthy checked against whether the target defines a healthcheck at all
- Port syntax validated in both short and long form, including IPv6 host addresses and ranges
- Duration fields checked for a unit, so interval: 30 is caught rather than silently misread
- Unknown keys reported as unchecked, so a clean result never means more than it should
How to use it
- Paste your compose.yaml, or use Open a file to load it from disk. JSON works too.
- Read the errors first — those are the ones that will stop docker compose up.
- Check the host ports panel against what is already running on your machine. This validator can see conflicts inside the file, but not a port your system has already taken.
- Turn warnings off if you only want blocking problems, or leave them on to catch :latest tags and obsolete keys.
- Read the Not checked panel before you treat a clean result as a guarantee.
Real-world use cases
DevOps & platform engineers
Catch a renamed service that broke depends_on, or a port collision between two services, before pushing a compose file that a teammate will pull and watch fail at up.
Backend developers
Debug why a local stack hangs forever waiting on a dependency, by checking whether the service it's waiting on actually defines a healthcheck at all — a compose file can be perfectly valid YAML and still deadlock on this.
Onboarding / new team members
Paste an inherited compose.yaml to see its shape at a glance — services, published ports, volumes and networks summarised — without tracing through fifty lines of YAML by hand on day one.
SREs reviewing infrastructure changes
Check a PR that touches compose.yaml for reference breaks before approving it, especially the kind that survives docker compose config and only fails once a teammate actually runs up.
Security-conscious engineers
Validate a compose file containing real credentials or internal hostnames without it ever leaving the browser tab — nothing is uploaded, unlike most hosted YAML linters.
Examples
A depends_on that points at nothing
services:
api:
image: node:22
depends_on:
- cache
redis:
image: redis:7error /services/api/depends_on "cache" is not a service in this file. Declared services: api, redis.
The service was renamed to redis and the dependency was not updated. Compose fails immediately, but only tells you the name it could not find.
Two services on one host port
services:
api:
image: node:22
ports: ["8080:3000"]
admin:
image: nginx:1.27
ports: ["8080:80"]error /services/admin/ports/0 Host port 8080/tcp is already published by service "api".
Whichever container starts second fails with "port is already allocated". The file is valid YAML and valid compose schema; only the combination is wrong.
Waiting on a healthcheck that does not exist
services:
api:
image: node:22
depends_on:
db:
condition: service_healthy
db:
image: postgres:16error /services/api/depends_on Waiting for "db" to be healthy, but "db" defines no healthcheck.
This is the one that wastes an afternoon: docker compose up hangs forever waiting for a health state the container will never report.
An interval with no unit
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 30error /services/db/healthcheck/interval `interval` must be a duration with a unit, such as 30s or 1m30s. Found 30.
A bare number is not a compose duration. Read as nanoseconds it would be 30ns, which is why the healthcheck appears to run constantly.
Common errors
| Message | Cause | Fix |
|---|---|---|
| service X depends on undefined service Y | A depends_on entry names a service that is not in the file — usually one that was renamed or moved into another compose file. | Correct the name, or remove the dependency. If the service genuinely lives in another file, both files have to be passed to compose together for the reference to resolve. |
| Bind for 0.0.0.0:8080 failed: port is already allocated | Two services publish the same host port, or something outside Docker already holds it. | This tool finds the first case. For the second, check what is listening (lsof -i :8080 on macOS and Linux, netstat -ano | findstr :8080 on Windows) and change the host side of the mapping. |
| dependency failed to start: container X is unhealthy | The target service defines a healthcheck and it never passed within the retry budget. | Run the healthcheck command inside the container by hand. A common cause is a test that needs a tool the image does not have — pg_isready exists in the postgres image, curl often does not. |
| Named volume must be used only with a service, not a bind mount | A volume source that is not a path was treated as a named volume, and there is no matching entry under the top-level volumes key. | Declare it (volumes: with the name and an empty value), or make it a bind mount by writing an actual path — ./data:/var/lib rather than data:/var/lib. |
| restart: no does nothing, or errors | Compose's YAML loader follows YAML 1.1, where the unquoted word no is the boolean false. The value never reaches the restart policy check as a string. | Quote it: restart: "no". This validator surfaces the ambiguity because its parser follows YAML 1.2, where the same value is a string — a difference that changes what your file means. |
| The `version` key is obsolete | The Compose Spec removed the version field. Docker Compose v2 ignores it entirely. | Delete the line. Nothing depends on it, and leaving it in suggests the file targets a schema version that no longer exists. |
Frequently asked questions
›Is my compose file uploaded anywhere?
No. Parsing and validation both happen in your browser, which is why this Docker Compose validator is safe to use on a file containing real credentials. Disconnect from the network and it keeps working — that is the only demonstration of the claim that means anything.
›How is this different from docker compose config?
docker compose config resolves variables, merges override files and re-emits the result, and it does catch schema errors. What it does not do is check the relationships between parts: it will happily print a config where depends_on names a service that does not exist, or where two services publish the same host port. Those are the checks here. Run both — they answer different questions, and only compose config can resolve your .env file.
›Why is a valid-looking key reported as not checked?
Because the schema here is not complete, and saying so is more useful than pretending otherwise. An unrecognised key is reported as a warning, never an error — it is usually a newer field or an extension. The rule matters: a validator that skips what it does not recognise and then prints "valid" has converted its own ignorance into your false confidence.
›Can it tell me whether my image exists?
No, and nothing that runs in a browser can. Checking an image means talking to a registry, which would mean sending your compose file to a server. Build contexts, Dockerfiles, env_file paths and bind-mount sources are all unreadable from here for the same reason. All five limits are listed in the Not checked panel rather than left for you to discover.
›Does it understand profiles, extends and include?
Profiles are collected and counted, and extends satisfies the requirement for an image or build. include and extends pointing at another file cannot be followed — this tool sees one file at a time — so references that resolve in the other file will be reported as missing here. That is a false positive worth knowing about, and it is the reason the reference checks are errors rather than silent.
›Why does it warn about :latest?
Because :latest is not a version, it is a moving pointer. The image your teammate pulls next week is not the one you tested, and there is no record of what changed. It is a warning rather than an error because it is a legitimate choice during development — but a tag or a digest is what makes an environment reproducible.
›What about Kubernetes manifests?
Different format, different tool — use the Kubernetes YAML validator, which shares this one's parser and follows the same rule about reporting what it did not check. Compose files and Kubernetes manifests overlap conceptually and share almost nothing structurally, so one validator for both would do neither well.
›Which Compose spec version does this validate against?
The Compose Specification — the merged, versionless schema that docker compose (v2, the compose plugin) has used since it absorbed the old docker-compose.yml v2/v3 file formats. That is why the version: key is flagged as obsolete rather than checked against a number: the field no longer means anything to the tool that reads your file. If you are still on the standalone Python docker-compose v1, its 2.x/3.x schemas differ in a few key names this validator does not model.
Last updated