How to convert a docker-compose.yml file to json
- Step 1Copy the compose file — Paste the full
docker-compose.yml, or drop the.yml/.yaml/.txtfile. Compose v2 (services at the top level) and the olderversion: "3.8"form both parse. - Step 2Keep multi-doc off — A compose file is a single YAML document, so leave Multi-document YAML (---) off. (You would only need it for an unusual concatenation of several files.)
- Step 3Pick an indent — Minified for a compact body fed to a converter or script; 2 or 4 spaces for a readable tree to inspect ports and dependencies by eye.
- Step 4Convert and explore — Click Convert to JSON. Top-level keys:
services,volumes,networks. Each service hasimage,ports,environment,volumes, anddepends_onas nested fields. - Step 5Extract what you need — Run a JSONPath in the JSON Path Extractor —
$.services.*.portsfor all port mappings,$.services.*.environmentto audit env values across services. - Step 6Feed into automation — Pass the JSON to a Compose-to-Kubernetes generator, a script that asserts required labels on every service, or a network diagram built from the
depends_ongraph.
How Compose constructs convert
The Compose-specific shapes and how each appears in the JSON.
| Compose construct | JSON result | Note |
|---|---|---|
environment: map (KEY: value) | JSON object | Keys and values preserved |
environment: list (- KEY=VALUE) | JSON array of strings | Each KEY=VALUE is one string element |
${DB_PASSWORD} reference | literal string "${DB_PASSWORD}" | Not interpolated — greppable for audits |
ports: - "8080:80" | array of strings | Mapping kept as written |
depends_on: [db] | array ["db"] | Long-form depends_on becomes an object |
x-common-config | regular JSON key | Extension fields preserved verbatim |
Converter controls and limits
The two UI controls. No variable interpolation, no docker compose config-style merging.
| Item | Value | Effect |
|---|---|---|
| Multi-document YAML (---) | on / off | Keep off for a single compose file |
| Indent | Minified · 2 · 4 spaces | Output formatting only |
| Accepted input | .yml .yaml .txt | Mime application/x-yaml, text/yaml, text/plain |
| Free tier limit | 2 MB, 1 file | Compose files are tiny — never a constraint in practice |
Cookbook
Compose snippets and the JSON they produce. The theme: env values and ${VARS} stay literal, so the JSON is audit-ready.
A two-service stack to JSON
Exampleservices, volumes, and networks land at the top level; each service keeps image, ports, environment, and depends_on.
Input (docker-compose.yml):
services:
web:
image: nginx:1.25
ports: ["8080:80"]
depends_on: [db]
db:
image: postgres:16
volumes:
pgdata:
Output JSON (2-space):
{
"services": {
"web": { "image": "nginx:1.25", "ports": ["8080:80"], "depends_on": ["db"] },
"db": { "image": "postgres:16" }
},
"volumes": { "pgdata": null }
}Both environment formats side by side
ExampleMap form becomes an object; list form becomes an array of KEY=VALUE strings. Both are valid Compose; the JSON shapes differ accordingly.
Input:
services:
a:
environment:
POSTGRES_USER: app
POSTGRES_DB: appdb
b:
environment:
- REDIS_HOST=cache
- REDIS_PORT=6379
Output JSON:
{ "services": {
"a": { "environment": { "POSTGRES_USER": "app", "POSTGRES_DB": "appdb" } },
"b": { "environment": ["REDIS_HOST=cache", "REDIS_PORT=6379"] }
} }Secrets audit — `${VARS}` stay literal
ExampleThe converter does not interpolate variables, so a secrets audit sees exactly which env vars feed each service.
Input:
services:
api:
environment:
DB_PASSWORD: ${DB_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
Output JSON (literal references):
{ "services": { "api": { "environment": {
"DB_PASSWORD": "${DB_PASSWORD}",
"JWT_SECRET": "${JWT_SECRET}"
} } } }
Audit: grep the JSON for `${` → every referenced var found.Extract every published port
ExampleConvert, then a single JSONPath lists all port mappings for a firewall or conflict review.
After converting docker-compose.yml to JSON, in the JSON Path Extractor: $.services.*.ports Result: [ ["8080:80"], ["5432:5432"], ["6379:6379"] ]
x- extension field with a real anchor
ExampleAn x- field used as a YAML anchor is both preserved (as a key) and resolved (where referenced). The alias is expanded inline.
Input:
x-logging: &logging
driver: json-file
options: { max-size: 10m }
services:
web:
logging: *logging
Output JSON (anchor resolved):
{
"x-logging": { "driver": "json-file", "options": { "max-size": "10m" } },
"services": { "web": { "logging": { "driver": "json-file", "options": { "max-size": "10m" } } } }
}Errors and edge cases
Real errors and silent failures sourced from each platform's own documentation. Match the wording to the row, fix what the row says to fix.
`${VAR}` you expected to be substituted
By designThe converter does not interpolate environment variables — there is no .env context and no docker compose config step. ${DB_PASSWORD} stays a literal string, which is exactly right for an audit. If you need the resolved value, run docker compose config locally first, then convert that output.
Empty volume or network entry
Null valueA bare pgdata: under volumes: (the common short form for a default-driver named volume) has no value, so it becomes "pgdata": null in the JSON. That is correct Compose semantics — a null-valued key means default settings.
Duplicate service name
Duplicate key errorTwo services with the same name under services: throw duplicated mapping key with the line number. Compose would also reject this. Rename one service before converting.
Tab indentation in the compose file
Tab errorA tab where spaces are expected throws tab characters must not be used in indentation. Compose files must use spaces; re-indent and re-run.
`version: "3.8"` quoting
PreservedThe legacy version field should be quoted ("3.8") so it stays a string. Unquoted version: 3.8 becomes the number 3.8, and version: 3.10 would become 3.1 — quote it to be safe. Compose v2 dropped the field, so this only matters for older files.
`x-` field referenced but no anchor used
Not mergedx- extension fields are preserved as plain keys but are not merged into services unless you actually wired them with a YAML anchor/alias or merge key. Just defining x-common and naming it in a comment does nothing — use &anchor / *alias for it to expand.
Long-form `depends_on` with conditions
Object outputThe long form depends_on: { db: { condition: service_healthy } } converts to a nested JSON object, not an array. Your dependency-graph code must handle both the short (array) and long (object) shapes.
Empty compose file
Undefined outputAn empty or comments-only file parses to undefined and outputs the literal undefined. Paste actual service definitions.
Unknown custom tag in a value
Unknown tag errorCompose files have no custom tags, but a stray !-prefixed value throws unknown tag. Remove or quote it. Only standard YAML 1.2 tags are recognised.
Quoted port like `"08080:80"`
PreservedPort mappings are normally quoted strings in Compose, so leading zeros and the colon survive. An unquoted bare number port (8080) becomes a JSON number — quote port mappings ("8080:80") as Compose recommends to keep them as strings.
Frequently asked questions
Does it substitute `${VAR}` environment variables?
No, and that is intentional. The converter has no .env context, so ${DB_PASSWORD} is kept as a literal string. This is ideal for a secrets audit — you can grep the JSON for ${ to find every variable each service references. For substituted values, run docker compose config locally and convert that output.
How are the two environment formats handled?
Both convert correctly. The map form (KEY: value) becomes a JSON object; the list form (- KEY=VALUE) becomes a JSON array of "KEY=VALUE" strings. They convert to different JSON shapes because they are different YAML structures — your tooling should expect either.
Are `x-` extension fields kept?
Yes. x-common-config, x-logging, and any other x--prefixed key is preserved verbatim as an ordinary JSON key. If you wired it up with a real YAML anchor/alias, the alias is resolved and the block is expanded where referenced.
How do I extract all published ports?
Convert the compose file to JSON, then run $.services.*.ports in the JSON Path Extractor. You get every service's port array — useful for spotting host-port conflicts or building a firewall rule list.
Why is my named volume `null` in the JSON?
A bare pgdata: under volumes: is the Compose short form for a named volume with default settings — it has no value, so it converts to "pgdata": null. That is correct; Compose interprets a null-valued volume key as default driver and options.
Will both Compose v2 and the old `version:` format work?
Yes. Both parse the same way — they are plain YAML. Quote the legacy version value ("3.8") so it stays a string; unquoted it becomes a number and 3.10 would lose its trailing zero. Compose v2 files have no version field, so this only affects older files.
Are database passwords in my environment block uploaded?
No. Conversion runs entirely in your browser with js-yaml. Database credentials, API keys, and internal hostnames in environment blocks stay on your machine — nothing reaches JAD Apps servers.
Can it generate Kubernetes manifests from my compose file?
No — it only converts YAML to JSON. But the JSON is the input many Compose-to-Kubernetes tools (like kompose) or your own scripts expect. Convert here, then feed the JSON to your generator.
Does `depends_on` survive for a startup-order graph?
Yes. The short form becomes a JSON array (["db"]) and the long form with health conditions becomes a nested object. Query $.services.*.depends_on to reconstruct the dependency graph; just handle both shapes in your code.
Why did I get `duplicated mapping key`?
Two services (or two keys in one mapping) share the same name. Compose would reject this too. Rename the duplicate so each service id is unique, then convert again.
Can I convert the JSON back to a compose file?
Yes, with the inverse JSON to YAML tool — handy if you generated or transformed a compose structure as JSON and need it back as docker-compose.yml.
How do I diff two compose files?
Convert both to JSON, then compare them with the JSON Diff tool to see exactly which services, ports, or env vars changed between two versions — clearer than a formatting-sensitive YAML diff.
Privacy first
Conversion runs locally in your browser. No file is uploaded — only metadata counters are saved for signed-in dashboard stats.