validating ...\q3\compose.yaml: services.web.image must be a string means the image key of the service has no value or has a block of keys under it. Write the image reference on the same line: image: alpine.

The test gave the same message for two different mistakes: image: left empty, and image: with a nested name: alpine below it. In both, the fix is one plain string.

The error

PS> docker compose -f compose.yaml config
validating ...\q3\compose.yaml: services.web.image must be a string

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

name: fix10-cy-q3
services:
  web:
    image:
      name: alpine
    command: ["sleep", "1"]

The empty variant, and its message:

name: fix10-cy-q3
services:
  web:
    image:
    command: ["sleep", "1"]
PS> docker compose -f compose.empty.yaml config
validating ...\q3\compose.empty.yaml: services.web.image must be a string

Why it happens

In YAML, image: followed by indented keys makes image a mapping. image: with nothing after it and nothing indented below makes it null. Compose wants a string for image, as the message says, and its validation names the key.

The nested form looks like the build: block, which does take keys under it, and that is an easy pattern to copy into the wrong key.

Because both mistakes print the same text, the message alone does not tell you which one you have. Open the file at the image line of the service it names: if the line ends after the colon, look at what is indented under it, or whether anything is there at all.

The fix

name: fix10-cy-q3
services:
  web:
    image: alpine
    command: ["sleep", "1"]
PS> docker compose -f compose.fixed.yaml config
name: fix10-cy-q3
services:
  web:
    command:
      - sleep
      - "1"
    image: alpine
...

With the image name on the same line as the key, the file validates and config prints it back as image: alpine, beside the command it expanded into a list. The same corrected line works for both broken files, since both failed on the same key: one file was missing the value, the other held it one level too deep.

How it was reproduced

Docker Desktop on Windows 11, Docker Compose v5.7.0, engine 29.9.3, Windows PowerShell 5.1. Two one-service files, one with image: holding a nested name: alpine and one with image: left empty, each gave the message above on docker compose -f ... config with exit code 1; with image: alpine the same command printed the model with exit code 0. No container was started. The project name fix10-cy-q3 is the test's own.

Frequently asked

What does services.web.image must be a string mean?
The image key has no value or has keys nested under it. Write the image reference on the same line, such as image: alpine.
Can image be empty in docker compose?
No. An empty image: gave services.web.image must be a string in the test, the same message as a nested mapping.
How do I write the image key in compose.yaml?
As one string on the same line: image: alpine. In the test image: alpine validated and config printed it back.

More decoded errors in the Fixes category. Another message of the same kind is services must be a mapping.