This error occurs when the healthcheck configuration in your docker-compose.yml file contains syntax errors, unsupported options, or conflicting settings. Docker Compose validates healthcheck definitions and rejects configurations that don't conform to the expected format or contain incompatible option combinations.
The "Service has an invalid healthcheck definition" error indicates that Docker Compose found a problem with how you've configured the healthcheck for a service in your compose file. Healthchecks define commands that Docker runs periodically to determine if a container is functioning properly. Docker Compose performs strict validation on healthcheck configurations before starting any containers. This validation ensures that all required fields are present, values are in the correct format, and no conflicting options are specified together. When validation fails, Compose stops immediately with this error rather than attempting to start containers with invalid configurations. Common validation failures include using the wrong format for the `test` command (mixing string and array formats incorrectly), combining `disable: true` with other healthcheck options, using invalid duration formats for timing parameters, or specifying options that don't exist or are misspelled.
Run docker compose config to see the specific validation error:
# Docker Compose V2
docker compose config
# Docker Compose V1 (legacy)
docker-compose configThis will show you the exact issue with your compose file. Common error messages include:
- "disable: true" cannot be combined with other options
- Unsupported config option for healthcheck: 'xxx'
- Invalid interpolation format
- healthcheck.test must be a string or list
Look at the error message to understand which specific option is causing the problem.
When disabling a healthcheck (often to override an image's default), you cannot combine disable: true with any other options:
# WRONG - disable cannot be combined with other options
services:
web:
image: nginx
healthcheck:
disable: true
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 30s
# CORRECT - disable alone
services:
web:
image: nginx
healthcheck:
disable: true
# ALTERNATIVE - use test: ["NONE"] instead of disable
services:
web:
image: nginx
healthcheck:
test: ["NONE"]If you want to override a healthcheck from a parent compose file or image, use the test: ["NONE"] format instead of disable: true, which allows you to include other options if needed.
The healthcheck test command has specific format requirements:
services:
web:
image: nginx
healthcheck:
# Option 1: String format (runs via /bin/sh -c)
test: curl -f http://localhost/ || exit 1
# Option 2: CMD-SHELL array (explicit shell execution)
test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"]
# Option 3: CMD array (direct execution, no shell)
test: ["CMD", "curl", "-f", "http://localhost/"]
# Option 4: Disable healthcheck
test: ["NONE"]Common mistakes to avoid:
# WRONG - Array without CMD or CMD-SHELL prefix
test: ["curl", "-f", "http://localhost/"]
# WRONG - CMD as a string in the command
test: "CMD curl -f http://localhost/"
# WRONG - Missing failure handling in shell format
test: ["CMD-SHELL", "curl http://localhost/"] # Should have || exit 1
# CORRECT
test: ["CMD", "curl", "-f", "http://localhost/"]Duration values must follow Go's duration format:
services:
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
# Valid duration formats
interval: 30s # 30 seconds
timeout: 10s # 10 seconds
retries: 3 # integer, not a duration
start_period: 1m30s # 1 minute 30 seconds
start_interval: 5s # 5 seconds (Docker 25.0+)Valid duration suffixes:
- s - seconds (e.g., 30s)
- m - minutes (e.g., 5m)
- h - hours (e.g., 1h)
- Combined: 1h30m, 1m30s
Invalid formats:
# WRONG - Missing unit
interval: 30
# WRONG - Invalid unit
timeout: 10sec
# WRONG - Quoted numbers
retries: "3"
# CORRECT
interval: 30s
timeout: 10s
retries: 3The healthcheck option requires compose file version 2.1 or higher:
# For Docker Compose V1 (legacy)
version: "2.1" # Minimum for healthcheck
# or
version: "3" # Also supports healthcheck
services:
web:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]For Docker Compose V2 (modern), the version field is optional but if present should not be "1" or "2.0":
# Docker Compose V2 - version is optional
services:
web:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]Check your Docker Compose version:
# Docker Compose V2
docker compose version
# Docker Compose V1 (legacy)
docker-compose versionNote: The start_interval option requires Docker Engine 25.0 or later. If you see an error about this option, either upgrade Docker or remove it.
When using shell variables in healthcheck tests, special characters can cause interpolation errors:
# WRONG - $? causes interpolation error
services:
web:
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost/ && echo $?"]
# CORRECT - Escape the dollar sign
services:
web:
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost/ && echo $$?"]
# BETTER - Use explicit exit
services:
web:
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"]For environment variables in healthchecks:
services:
db:
image: postgres:16
environment:
POSTGRES_USER: myuser
healthcheck:
# Use shell variable syntax carefully
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"]
# Or use the direct value
test: ["CMD-SHELL", "pg_isready -U myuser"]YAML indentation errors can cause healthcheck options to be parsed incorrectly:
# WRONG - Inconsistent indentation
services:
web:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 30s # This is at wrong level - not part of healthcheck!
# CORRECT - All options indented under healthcheck
services:
web:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40sUse a YAML linter to check your file:
# Using yamllint
yamllint docker-compose.yml
# Or validate with docker compose
docker compose config --quiet && echo "Valid" || echo "Invalid"Also ensure you're using spaces, not tabs, for indentation in YAML files.
Healthcheck inheritance and overrides: When extending services or using multiple compose files, healthcheck definitions can conflict. A child service cannot partially override a parent's healthcheck - it must completely replace it or disable it:
# base.yml
services:
web:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 30s
timeout: 10s
# override.yml - This completely replaces the healthcheck
services:
web:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 60s
timeout: 20sCompose Specification vs legacy versions: The modern Compose Specification (used by Docker Compose V2) merges the features of versions 2.x and 3.x. Some options that were version-specific are now universally available. If migrating from an older compose file, you may encounter validation differences.
Image healthchecks vs compose healthchecks: If your image has a HEALTHCHECK instruction in its Dockerfile, the compose file healthcheck will override it. To inherit the image's healthcheck, omit the healthcheck section entirely. To disable it, use disable: true or test: ["NONE"].
Debugging with --dry-run: Test your compose configuration without starting containers:
docker compose up --dry-runThis validates the entire configuration including healthchecks and shows what would happen.
Schema validation: You can validate your compose file against the official JSON schema:
# Using check-jsonschema
pip install check-jsonschema
check-jsonschema --schemafile https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json docker-compose.ymlimage operating system "linux" cannot be used on this platform
How to fix 'image operating system linux cannot be used on this platform' in Docker
manifest unknown: manifest unknown
How to fix 'manifest unknown' in Docker
cannot open '/etc/passwd': Permission denied
How to fix 'cannot open: Permission denied' in Docker
Error response from daemon: failed to create the ipvlan port
How to fix 'failed to create the ipvlan port' in Docker
toomanyrequests: Rate exceeded for anonymous users
How to fix 'Rate exceeded for anonymous users' in Docker Hub