DevBolt
By The DevBolt Team··12 min read

Docker Compose Errors: The 10 Most Common YAML Mistakes and How to Fix Them

DockerDevOpsYAMLHowTo

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:

Terminal
yaml: line 8: found character that cannot start any token

This 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.

Terminal
services.web.ports must be a list

You wrote ports: "8080:80" as a scalar value instead of a list. Ports must always be a YAML list (with - dashes).

Terminal
services.api.depends_on contains an invalid type, it should be an array

You 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.

Terminal
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.

Terminal
yaml: unmarshal errors:
  line 3: cannot unmarshal !!str into compose.ServiceConfig

Docker 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.

Wrong
services:
	web:                # tab character - YAML parser will reject this
		image: nginx
Correct
services:
  web:                # two spaces
    image: nginx

Fix: 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.

Wrong
ports:
  - 80:80              # parsed as integer 4880, not "80:80"
  - 8080:80            # parsed as integer 488480
Correct
ports:
  - "80:80"            # quoted string
  - "8080:80"
  - "127.0.0.1:3000:3000"

# Or use long syntax:
ports:
  - target: 80
    published: 8080
    protocol: tcp

Fix: 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.

Outdated (V1-era)
version: "3.8"       # deprecated in Compose V2
services:
  web:
    image: nginx
Modern (Compose V2)
services:            # no version field needed
  web:
    image: nginx

Fix: 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.

Wrong
web:                   # missing "services:" parent key
  image: nginx
  ports:
    - "80:80"
Correct
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.

Common mistakes
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 host
Correct
services:
  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 here

Fix: 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 :.

List syntax (with =)
environment:
  - NODE_ENV=production
  - DATABASE_URL=postgres://user:pass@db:5432/app
Map syntax (with :)
environment:
  NODE_ENV: production
  DATABASE_URL: postgres://user:pass@db:5432/app
Wrong (mixed syntax)
environment:
  - NODE_ENV: production       # combining - (list) with : (map) = error
  DATABASE_URL=postgres://...  # missing - dash for list item

Fix: 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.

Short syntax (starts container, doesn't wait for ready)
depends_on:
  - db
  - redis
Long syntax with conditions (Compose V2)
depends_on:
  db:
    condition: service_healthy
    restart: true              # restart this service if db restarts
  redis:
    condition: service_started

Fix: 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.

Wrong
networks:
  mynet:
    external: true         # fails if "mynet" doesn't already exist

# Also wrong:
services:
  web:
    networks:
      - nonexistent        # network not declared in top-level networks
Correct
# 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_net

Fix: 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.

Wrong
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 file
Correct
services:
  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 context

Fix: 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.

Duplicate key (silent data loss)
services:
  api:
    image: node:22
    ports:
      - "3000:3000"

  api:                       # silently overwrites the first "api"
    image: python:3.13
    ports:
      - "8000:8000"
Port conflict (runtime error)
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.

Terminal
$ 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.

Terminal
$ docker compose up --dry-run
[+] Dry run
 - Container myapp-db-1      Created
 - Container myapp-redis-1   Created
 - Container myapp-api-1     Created

Step 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:

Terminal
# 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 api

Pay 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 uses docker compose (space, Docker CLI plugin). The V1 binary is no longer maintained. If you see docker-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_on supported condition: 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 profiles to selectively enable services. If you use profiles in a V1 environment, it will be silently ignored or throw an unknown field error.
  • File naming: V1 expected docker-compose.yml. V2 prefers compose.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" not 8080: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_file to load from an external file and add .env to your .gitignore.
  • Validate before deploying. Run docker compose config locally, then paste your file into a Docker Compose Validator to catch schema issues that config misses.
  • Pin image versions. Use postgres:16-alpine not postgres:latest. The latest tag changes without warning and can introduce breaking changes.
  • Add healthchecks to databases. Use depends_on with condition: service_healthy instead 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.