DEV Community

Cover image for Docker Compose network declared as external, but not found
Kay
Kay

Posted on Originally published at latchkey.dev

Docker Compose network declared as external, but not found

"network shared-net declared as external, but could not be found" means Compose refused to start because a docker compose network declared as external (external: true) must already exist, and Compose never creates one. Create it in the same job with docker network create shared-net before docker compose up, or remove external: true so Compose owns the network; our recorded run fails and passes on exactly that one difference.

Runner log: one compose file starting with the network present and failing without it
The recorded run: the control brings the stack up with the network created by hand, then the network is removed and the same command fails on the lookup.

Diagram of who creates a compose network and what external changes about it
Compose owns the networks it creates and names them after the project. External moves both responsibilities to you, including the exact name.

What this error means

A docker compose up fails immediately, before any image is pulled and before any container is created, naming the network rather than a service. Nothing in the file is invalid: the same file works the moment the network exists, which is what makes this confusing on a runner, where it works on a developer machine that has had the network since last month. The recorded run below created the network, ran the stack to completion, removed the network, and ran the identical command again.

```Actions log, docker compose up, Compose v5.5.0
--- control: create the network, then up
Container compose-extnet-demo-web-1 Started
web-1 exited with code 0
--- remove the network, run the same command again
bridge host none
network shared-net declared as external, but could not be found




## Common causes

### Nothing created the network in this job

The dominant cause on a runner. The file was written on a machine where the network already existed, and the job it runs in has no create step. Our recorded run reproduces exactly this: the same file, the same command, and the only difference is whether the network is there.

### The name does not match the network that exists

Compose prefixes the networks it creates with the project name, so a network another project created is rarely called what your key calls it. The declaration names a network that does not exist while a nearly identical one does, which reads as a Compose bug and is a naming mismatch.

### A prune removed it between steps

Cleanup steps are cheerfully destructive: an unused network has no running containers attached and is exactly what a system prune collects. In our experience this is the cause whenever the same job creates the network successfully and fails on it later.

### The network belongs to another runner or another job

A network created in one job does not exist in the next, because the next job starts on a different machine. The same applies to a self-hosted runner that was recycled between jobs, and to any step that expects yesterday state on a fresh runner.

## How to fix it

### Create the network in the job, idempotently

1. Inspect first, create only if the inspect fails, so a second run is harmless.
2. Put it before the step that starts the stack, in the same job.
3. Remove it in a cleanup step only if the job created it.



```.github/workflows/ci.yml
docker network inspect shared-net >/dev/null 2>&1 \
  || docker network create shared-net
docker compose up -d --wait
Enter fullscreen mode Exit fullscreen mode

Let Compose own the network instead

If no other stack attaches to it, drop the external declaration and let Compose create and remove the network with the project. That deletes the failure mode rather than working around it, and it removes a cleanup step you were probably not running.

# before
networks:
  shared-net:
    external: true

# after
networks:
  shared-net: {}
Enter fullscreen mode Exit fullscreen mode

Name the real network when it is genuinely shared

When another project owns the network, write its actual name with the name attribute. Take the name from the network list rather than from the other project's compose file, because the prefix comes from the project name in force when it was created.

docker network ls --format '{{.Name}}'
# then, in compose.yaml:
#   networks:
#     shared-net:
#       external: true
#       name: tools_shared-net
Enter fullscreen mode Exit fullscreen mode

Keep prune away from the stack

Scope cleanup to what the job created, and run it at the end rather than between steps. A targeted compose down removes the project's own containers and networks and leaves shared ones alone, which a system prune does not.

docker compose down --remove-orphans   # not: docker system prune -f
Enter fullscreen mode Exit fullscreen mode

How to prevent it

  • Create shared networks in the job that needs them, before the stack starts.
  • Use external: true only where another stack genuinely owns the network.
  • Write the real network name with name: rather than relying on a prefix.
  • Keep prune steps at the end of a job and scoped to what the job created.

External means you own it

The Compose networks reference we read on 2026-09-20 puts it in one sentence: Compose does not attempt to create these networks, and returns an error if one does not exist. The same page notes that every attribute other than the name is irrelevant on an external network, and that a file setting any of them is rejected as invalid.

The reason to mark a network external is that another stack owns it. If nothing else uses it, the declaration costs you a setup step, a cleanup step and this failure, and buys nothing.

Declaration Who creates it What the name is
A plain network entry Compose, on up The project name plus the key, for example api_default
external: true You, before up Exactly the key, unless name: says otherwise
external: true with name: You, before up Exactly the value of name:
No networks block at all Compose, on up The project default network

Behavior quoted from the Compose networks top-level element reference, read on 2026-09-20, and matched against our own run the same day.

Why it is a CI failure and not a laptop failure

A developer machine accumulates networks. Somebody ran the create command once, months ago, and every stack since has found it waiting. A runner starts with the default networks and nothing else, which our recorded run shows in the line before the failure: bridge, host and none, and no project network of any kind.

That makes this a pipeline ordering question rather than a compose question. Whatever creates the network has to run in the same job, before the stack starts, and it has to be idempotent because the job may run twice on the same runner in a self-hosted setup.

- run: docker network inspect shared-net >/dev/null 2>&1 || docker network create shared-net
- run: docker compose up -d --wait
Enter fullscreen mode Exit fullscreen mode

When the network exists and the lookup still fails

Then the names do not match, and the usual reason is that Compose prefixes the networks it creates with the project name. A network created earlier by another Compose project is called something like tools_shared-net, and a file declaring shared-net as external will not find it. The fix is to say the real name with the name attribute rather than renaming anything.

The other reason is timing rather than spelling: a cleanup step running docker system prune between two steps of the same job removes unused networks, and a network with no running containers attached qualifies. If your log shows a create, a success and then a failure later in the same job, look for the prune.

networks:
  shared-net:
    external: true
    name: tools_shared-net
Enter fullscreen mode Exit fullscreen mode

What the runner does about it

No repair, and the recorded run shows the round trip: the wrapper posted the failure to the sidecar, the sidecar answered, and the job ended on the same lookup. Latchkey has no pattern for this and should not have one. Creating a network that a compose file declared external would be a runner inventing the thing whose absence is the error, and on a shared network that is somebody else's resource.

FAQ

Does docker compose create external networks?

No. The networks reference states that Compose does not attempt to create them and returns an error if one does not exist. That is the entire meaning of the flag: it moves the lifecycle of that network out of the compose file, so creating and removing it becomes your job or another project's job.

What does external true mean in docker compose?

It tells Compose to attach to a network that already exists on the platform instead of creating one. Every attribute other than the name is then irrelevant, and the documentation says a file that sets one is rejected as invalid. Use it when a second stack, or something outside Compose, owns the network.

How do I create a docker network before compose up in CI?

Add a step in the same job that inspects the network and creates it only if the inspect fails, then start the stack. Keep it idempotent: on a self-hosted runner the job may run twice on the same machine, and an unconditional create fails the second time with an error about the name already being in use.

Why does my compose network have a project name prefix?

Because Compose names the networks it creates after the project plus the key in the file, so a network key of shared-net in a project called tools becomes tools_shared-net. An external declaration does no prefixing, which is why a name that another project created has to be written out in full with the name attribute.

References

Top comments (0)