Docker Compose errors are almost always caused by YAML syntax mistakes or configuration misunderstandings. The most common culprits: indentation errors (tabs instead of spaces), wrong value types in port mappings, missing required fields, and version conflicts between Compose V1 and V2. This guide decodes the error messages, shows you the 10 most frequent mistakes, and gives you a step-by-step debugging workflow.
Docker Compose Error Messages Decoded
Docker Compose error messages are notoriously cryptic. Here are the most common ones and what they actually mean:
yaml: line 8: found character that cannot start any tokenThis almost always means you have a tab character somewhere in your YAML. YAML forbids tabs for indentation — only spaces are allowed. Your editor may have inserted a tab without you noticing.
services.web.ports must be a listYou wrote ports: "8080:80" as a scalar value instead of a list. Ports must always be a YAML list (with - dashes).
services.api.depends_on contains an invalid type, it should be an arrayYou mixed up the short syntax (array of strings) with the long syntax (map with conditions). You can't combine both in the same depends_on block.
service "web" refers to undefined volume "app_data"You referenced a named volume in a service but forgot to declare it in the top-level volumes: section.
yaml: unmarshal errors:
line 3: cannot unmarshal !!str into compose.ServiceConfigDocker expected a mapping (key-value pairs) but found a plain string. This usually means your indentation is wrong and a service definition is being parsed as a value instead of a nested object.
The 10 Most Common Docker Compose Errors
1. YAML Indentation Errors (Tabs vs Spaces)
The number one cause of Docker Compose failures. YAML requires spaces for indentation — tabs are illegal. Most editors default to tabs, so a single invisible tab character breaks your entire file.
services:
web: # tab character - YAML parser will reject this
image: nginxservices:
web: # two spaces
image: nginxFix: Configure your editor to insert spaces when you press Tab. In VS Code, set "editor.insertSpaces": true and use 2-space indentation for YAML files. Run your file through our YAML Formatter to catch invisible tab characters.
2. Invalid Port Format
Port mappings in Docker Compose have a subtle gotcha: YAML interprets 80:80 as a base-60 number (sexagesimal), not a string. You must quote port mappings or the parser will silently convert them to an integer.
ports:
- 80:80 # parsed as integer 4880, not "80:80"
- 8080:80 # parsed as integer 488480ports:
- "80:80" # quoted string
- "8080:80"
- "127.0.0.1:3000:3000"
# Or use long syntax:
ports:
- target: 80
published: 8080
protocol: tcpFix: Always wrap port mappings in quotes. This applies to any value containing a colon that should remain a string.
3. Wrong version Field
The version field at the top of a Compose file was required in Compose V1 but is deprecated in Compose V2. It still causes confusion because old tutorials tell you to include it, and newer versions of Docker Compose will print warnings or ignore features based on the version you specify.
version: "3.8" # deprecated in Compose V2
services:
web:
image: nginxservices: # no version field needed
web:
image: nginxFix: If you are using Docker Compose V2 (the docker compose command with a space), remove the version field entirely. It does nothing and may trigger misleading warnings.
4. Missing or Invalid services Top-Level Key
Every Compose file needs a services key at the root level. A common mistake is putting service definitions directly at the top level or misspelling services.
web: # missing "services:" parent key
image: nginx
ports:
- "80:80"services:
web:
image: nginx
ports:
- "80:80"Fix: Make sure services: is at the root level of your file, with all service definitions indented beneath it.
5. Volume Mount Path Errors
Volume mounts break in several ways: relative paths that don't resolve correctly, Windows-style backslashes, and forgetting to declare named volumes.
volumes:
# Windows backslashes - won't work
- C:\Users\me\app:/app
# Missing named volume declaration
- mydata:/var/lib/data # error if not declared in top-level volumes
# Relative path that doesn't exist
- ../shared:/app/shared # fails if ../shared doesn't exist on hostservices:
api:
volumes:
- ./src:/app/src # relative bind mount
- /home/me/app:/app # absolute bind mount
- mydata:/var/lib/data # named volume
volumes:
mydata: # must be declared hereFix: Use forward slashes even on Windows (Docker translates them). Always declare named volumes in the top-level volumes: section. Use ./ to make relative paths explicit.
6. Environment Variable Syntax Errors
Docker Compose supports two environment variable formats, and mixing them up causes subtle bugs. The list format uses = while the map format uses :.
environment:
- NODE_ENV=production
- DATABASE_URL=postgres://user:pass@db:5432/appenvironment:
NODE_ENV: production
DATABASE_URL: postgres://user:pass@db:5432/appenvironment:
- NODE_ENV: production # combining - (list) with : (map) = error
DATABASE_URL=postgres://... # missing - dash for list itemFix: Pick one format and stick with it. For values containing special characters, use env_file and load from an external .env file. Validate your env files with our .env File Validator.
7. depends_on Condition vs Short Syntax
The depends_on field has two forms. The short syntax is an array of service names. The long syntax is a map with conditions. You cannot mix them, and trying to use conditions with the short syntax silently does nothing.
depends_on:
- db
- redisdepends_on:
db:
condition: service_healthy
restart: true # restart this service if db restarts
redis:
condition: service_startedFix: If your app crashes because the database isn't ready, switch to the long syntax with condition: service_healthy and add a healthcheck to the database service. The short syntax only guarantees the container has started, not that the process inside is accepting connections.
8. Network Configuration Errors
Custom networks are powerful but easy to misconfigure. The most common mistakes are referencing an external network that doesn't exist or specifying an invalid driver.
networks:
mynet:
external: true # fails if "mynet" doesn't already exist
# Also wrong:
services:
web:
networks:
- nonexistent # network not declared in top-level networks# Create the network if it doesn't exist:
networks:
frontend:
driver: bridge
backend:
driver: bridge
# Or for pre-existing networks:
networks:
proxy_net:
external: true # must exist: docker network create proxy_netFix: Declare all networks in the top-level networks: section. If using external: true, create the network first with docker network create.
9. Build Context Errors
When using build instead of image, the context path and Dockerfile location must be correct relative to the Compose file.
services:
api:
build: /api # absolute path, probably not what you want
# or
build:
context: ./api
dockerfile: api/Dockerfile # path is relative to context, not compose fileservices:
api:
build: ./api # Dockerfile must be at ./api/Dockerfile
# Or with explicit Dockerfile path:
api:
build:
context: . # build context is project root
dockerfile: docker/api.Dockerfile # relative to contextFix: The context sets the root directory for the build. The dockerfile path is relative to the context, not to the Compose file. When in doubt, use context: . and provide the full relative path in dockerfile. Validate your Dockerfiles with our Dockerfile Validator.
10. Duplicate Service Names or Port Conflicts
YAML maps silently overwrite duplicate keys. If you define two services with the same name, the second one wins and the first disappears without any warning. Port conflicts are louder — Docker will refuse to start if two services try to bind to the same host port.
services:
api:
image: node:22
ports:
- "3000:3000"
api: # silently overwrites the first "api"
image: python:3.13
ports:
- "8000:8000"services:
web:
ports:
- "80:80"
proxy:
ports:
- "80:8080" # port 80 on host is already taken by "web"Fix: Use unique service names and unique host ports. Run your Compose file through a Docker Compose Validator to catch duplicate keys that YAML parsers won't warn you about.
How to Debug Docker Compose Errors: Step-by-Step
When your Compose file won't work, follow this sequence to isolate the problem:
Step 1: Run docker compose config
This command parses your Compose file, resolves all variables, merges override files, and prints the final resolved configuration. If there is a YAML syntax error, it will tell you exactly which line.
$ docker compose config
yaml: line 12: found character that cannot start any token
# If it succeeds, you'll see the fully resolved config:
$ docker compose config
name: myproject
services:
web:
image: nginx:alpine
ports:
- "8080:80"Step 2: Validate the YAML Syntax
If docker compose config gives you a cryptic YAML error, paste your file into a dedicated YAML validator. It will highlight the exact position of syntax errors, including invisible characters like tabs or non-breaking spaces. Try our YAML Formatter & Validator — paste your Compose file and it will point to the exact line and column.
Step 3: Use docker compose up --dry-run
Compose V2 supports a --dry-run flag that simulates starting your stack without actually creating containers. This catches configuration errors like missing images, invalid build contexts, and port conflicts before you commit to a full startup.
$ docker compose up --dry-run
[+] Dry run
- Container myapp-db-1 Created
- Container myapp-redis-1 Created
- Container myapp-api-1 CreatedStep 4: Run a Compose-Specific Validator
YAML validators catch syntax errors but miss Docker Compose-specific issues like invalid service options, wrong field types, or deprecated keys. Use our Docker Compose Validator to check for schema-level issues: unknown keys, wrong types, missing required fields, and common anti-patterns.
Step 5: Read Service Logs
If the Compose file is valid but a service crashes on startup, check the logs:
# All services
$ docker compose logs
# Specific service (most useful)
$ docker compose logs api
# Follow logs in real time
$ docker compose logs -f api
# Show last 50 lines
$ docker compose logs --tail 50 apiPay attention to exit codes. An exit code of 1 usually means an application error. Exit code 137 means the container was killed (likely an out-of-memory issue). Exit code 127 means the entrypoint command was not found.
Docker Compose V1 vs V2: Key Differences That Cause Errors
Many Docker Compose errors come from following outdated tutorials written for V1. Here are the critical differences:
- Command name: V1 used
docker-compose(hyphen, standalone binary). V2 usesdocker compose(space, Docker CLI plugin). The V1 binary is no longer maintained. If you seedocker-compose: command not found, you need the V2 plugin. - Version field: V1 required a
version: "3.x"field. V2 ignores it and prints a warning. Remove it to keep your file clean. - depends_on conditions: In V1 (file version 2.x),
depends_onsupportedcondition: service_healthy. In V1 (file version 3.x), conditions were removed for Swarm compatibility. V2 brings them back. If conditions aren't working, check whether you are actually running Compose V2. - Profiles: V2 introduced
profilesto selectively enable services. If you useprofilesin a V1 environment, it will be silently ignored or throw an unknown field error. - File naming: V1 expected
docker-compose.yml. V2 preferscompose.yaml(but still supports the old name). The canonical extension is.yaml, not.yml.
Check your version with docker compose version. If the output shows "Docker Compose version v2.x.x", you are on V2.
Best Practices to Avoid Compose Errors
Most Docker Compose errors are preventable with a few habits:
- Always use spaces, never tabs. Configure your editor to use 2-space indentation for YAML files. This prevents the most common class of errors.
- Quote all port mappings. Write
"8080:80"not8080:80. Unquoted colon-separated numbers are interpreted as sexagesimal integers in YAML 1.1. - Use .env files for variables. Keep sensitive values out of your Compose file. Use
env_fileto load from an external file and add.envto your.gitignore. - Validate before deploying. Run
docker compose configlocally, then paste your file into a Docker Compose Validator to catch schema issues thatconfigmisses. - Pin image versions. Use
postgres:16-alpinenotpostgres:latest. Thelatesttag changes without warning and can introduce breaking changes. - Add healthchecks to databases. Use
depends_onwithcondition: service_healthyinstead of the short syntax. A started container is not a ready container.
Deploying Docker Compose stacks to production?
DigitalOcean Droplets give you dedicated CPU and memory starting at $4/mo — spin up a Docker host in 55 seconds with one-click Docker pre-installed.
Validate Your Compose Files Online
Stop guessing and start validating. Use our Docker Compose Validator to catch syntax errors, schema violations, and common anti-patterns before they break your deployment. Building custom images? Run your Dockerfiles through the Dockerfile Validator to catch security issues and layer optimization mistakes. And if your YAML is the problem, the YAML Formatter will format and validate it in one step. All tools run entirely in your browser — your configuration files never leave your machine.