services.web.ports must be a array — the ports key of the web service holds a single string, and Compose's file check wants a list. Put each mapping on its own line under ports:, starting with a dash, even when there is only one.

The fix is one line break: ports: "127.0.0.1:15880:80" becomes ports: followed by - "127.0.0.1:15880:80" on the next line, indented under it. The message reads "a array", not "an array"; that is the wording this Compose version printed, so search for it as it is.

The error

PS> docker compose up -d
validating C:\...\ports\compose.yaml: services.web.ports must be a array

The folder path in the first line was shortened. The command exited with code 1 and created nothing. The file that produced it:

name: fix8-d3-ports
services:
  web:
    image: alpine
    command: ["sleep", "5"]
    ports: "127.0.0.1:15880:80"

Why it happens

A service can publish several ports, so ports is a list in the Compose file format, and a YAML list is written as lines that start with - (or as ["..."] on one line; ports: ["127.0.0.1:15880:80"] passed docker compose config --quiet with exit code 0). A value after the colon on the same line, without brackets, is a plain string. There was no YAML parse error; the message starts with validating and names the path of the wrong key, services.web.ports, and the type it expected.

The path in the message tells you where to look: the service name sits between services. and the key. In a file with many services that is the fastest way to find the line.

The fix

name: fix8-d3-ports
services:
  web:
    image: alpine
    command: ["sleep", "5"]
    ports:
      - "127.0.0.1:15880:80"
PS> docker compose -f compose-fixed.yaml up -d
...
 Container fix8-d3-ports-web-1 Started
PS> docker compose -f compose-fixed.yaml ps --format '{{.Name}} {{.Ports}}'
fix8-d3-ports-web-1 127.0.0.1:15880->80/tcp

With the dash the project started and docker compose ps showed the mapping 127.0.0.1:15880->80/tcp. The quotes around the mapping stayed as they were; only its place in the file changed. The address in front, 127.0.0.1, is part of the mapping and was kept in the output of docker compose ps.

How it was reproduced

One alpine service running sleep 5 with ports: "127.0.0.1:15880:80" on a single line, started with docker compose up -d from Windows PowerShell 5.1 on Docker Engine 29.9.2 and Docker Compose v5.6.1 (Docker Desktop on Windows 11). The corrected file was saved as compose-fixed.yaml beside it and started with -f. The fix8-d3- names are this test's. The container and network were removed with docker compose down.

Frequently asked

What does 'services.web.ports must be a array' mean?
The ports key of the web service holds a single string instead of a list. Compose's validating step rejects the file and starts nothing.
How do I write ports in docker-compose.yml?
Write ports: on its own line and each mapping below it on a line that starts with a dash, for example - "127.0.0.1:15880:80". Use the list form even for one mapping.
Why does the Compose error say 'a array' instead of 'an array'?
That is the wording Docker Compose v5.6.1 printed in this test. Search for the message exactly as printed; the fix is the same whatever the grammar.

More decoded errors in the Fixes category.