Docker Compose "services.<name> must be a mapping" in CI
Compose expects each service to be a mapping of keys (image, ports, ...). services.<name> must be a mapping means the service was written as a scalar, a list, or left empty - so its body is not the key/value structure Compose requires.
What this error means
A docker compose config/up fails with services.<name> must be a mapping (or ... contains an invalid type, it should be an object). The named service has no proper key/value body.
services.api must be a mapping
# services:
# api: <- nothing indented under it, so it parses as null
# web:
# image: nginxDiagnose it: read the resolved config, not the file you wrote
Compose merges override files, interpolates variables, and applies defaults before it does anything. Most Compose failures in CI are visible in the resolved configuration and invisible in the source file, because the value you are debugging came from an unset variable that quietly became an empty string.
# the fully merged, interpolated configuration Compose will actually run
docker compose config
# fail loudly on unset variables instead of silently interpolating empty
docker compose --env-file .env config --quiet || echo "invalid"
# which override files were picked up
docker compose config --services
Common causes
An empty service body
A service key with nothing indented beneath it parses as null, not a mapping, and Compose rejects it.
The service written as a scalar or list
Assigning a string or a YAML list to a service name instead of a key/value block fails the mapping requirement.
Indentation collapses the service body
Under-indented keys that do not nest under the service name leave the service effectively empty.
How to fix it
Give the service a proper mapping body
Indent at least one key (like image) under the service name.
services:
api:
image: myorg/api:1.4.2
ports:
- "8080:8080"Remove or complete placeholder services
Delete empty service stubs or fill them in.
docker compose config # confirms each service is a valid mappingBind mounts behave differently on a runner
- A relative bind source is resolved against the compose file location, not the working directory of the shell that invoked it.
- The host path must exist before
up. Compose creates missing directories for named volumes but not for bind mounts, and the failure surfaces as a mount error rather than a missing-path error. - On a CI runner the workspace path differs from your machine, so any absolute host path in a compose file is a portability bug waiting for its first CI run.
- Prefer named volumes for anything that does not genuinely need to be read from the host. They remove the whole class of problem.
How to prevent it
- Give each service at least an
imageorbuildkey. - Remove placeholder/empty service entries.
- Run
docker compose configto validate service structure.