yaml: construct errors: line 1: line 9: mapping key "web" already defined at line 3 means the same name appears twice in one mapping of your Compose file. In the test it was a second service called web. Rename one of them, or merge the two blocks into one.

The message names both places: the second web on line 9 and the first on line 3. Decide which block you meant to keep; Compose will not pick one for you.

The error

PS> docker compose -f compose.yaml config
failed to parse ...\p5\compose.yaml: yaml: construct errors: line 1: line 9: mapping key "web" already defined at line 3

The file (the long path in the message is shortened above):

name: fix10-cy-p5
services:
  web:
    image: alpine
    command: ["sleep", "1"]
  worker:
    image: alpine
    command: ["sleep", "1"]
  web:
    image: alpine
    command: ["echo", "second web"]

Why it happens

A YAML mapping cannot hold the same key twice, and services is a mapping whose keys are the service names. Every line of the file is well formed; the message starts with failed to parse and the file path, says construct errors, and gives the line of the second key and then the line of the first.

The same check applies inside a service. A file with image written twice under web gave the same message for that key:

PS> docker compose -f compose.dupkey.yaml config
failed to parse ...\p5\compose.dupkey.yaml: yaml: construct errors: line 1: line 6: mapping key "image" already defined at line 4

A duplicate can turn up when a service block is copied to make a second one and the name is not changed, or when two people add the same key in a merge.

The fix

name: fix10-cy-p5
services:
  web:
    image: alpine
    command: ["sleep", "1"]
  worker:
    image: alpine
    command: ["sleep", "1"]
  web2:
    image: alpine
    command: ["echo", "second web"]
PS> docker compose -f compose.fixed.yaml config
name: fix10-cy-p5
services:
  web:
    command:
      - sleep
      - "1"
    image: alpine
    networks:
      default: null
  web2:
    command:
      - echo
      - second web
    image: alpine
    networks:
      default: null
  worker:
...

With the second block renamed to web2, the file parses and config lists three services (the output is cut after the second). If the two blocks were meant to be one service, merge their keys into a single web block instead, keeping each key once.

How it was reproduced

Docker Desktop on Windows 11, Docker Compose v5.7.0, engine 29.9.3, Windows PowerShell 5.1. A compose.yaml with three service blocks, two of them named web, gave the message above on docker compose -f compose.yaml config with exit code 1, and a second file with image twice in one service gave the "image" variant; the renamed file printed the model with exit code 0. No container was started. The project name fix10-cy-p5 is the test's own.

Frequently asked

What does mapping key already defined at line mean in docker compose?
The same key appears twice in one YAML mapping. In the test it was two services named web; the message gave the line of each.
Can two docker compose services have the same name?
No. Service names are the keys of the services mapping, and a second web was refused with mapping key "web" already defined.
Does a duplicate key inside one service give the same error?
Yes. Writing image twice under one service gave mapping key "image" already defined at line 4, with the line of the second one before it.

More decoded errors in the Fixes category. For indentation mistakes that stop the parser earlier, see did not find expected key.