Skip to content
Latchkey LogoLatchkey home

Docker Compose network declared as external, but not found

A docker compose network declared as external is one you promised already exists, and Compose takes that promise literally: it looks the network up by name when the stack starts and refuses to start anything when the lookup fails. Our recorded run shows the same compose file starting cleanly and then failing, with nothing changed between the two except whether the network was there.

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

Reproduced on a Latchkey runner

Run 2026-09-20·Runner latchkey-small·Exit code 1

Docker version 29.7.2, build a7dcaa6
Docker Compose version v5.5.0
docker.io/library/alpine:3
--- control: create the network, then up
596b4d636dd567d504b505e28d5ee3d21c34be73ee2dfa2087a2b9571fe3a9ba
 Container compose-extnet-demo-web-1 Starting 
 Container compose-extnet-demo-web-1 Started 

web-1 exited with code 0
--- remove the network, run the same command again
shared-net
bridge host none 
network shared-net declared as external, but could not be found
[latchkey-bash-wrapper] BEGIN sidecar POST (boot_wait=30s max_time=320s url=http://localhost/diagnose socket=/run/latchkey-self-heal/sock)
[latchkey-bash-wrapper] END sidecar POST ok (attempts=1 http=200)

The runner diagnosed the failure and did not retry it; this failure needs the fix below.

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.

DeclarationWho creates itWhat the name is
A plain network entryCompose, on upThe project name plus the key, for example api_default
external: trueYou, before upExactly the key, unless name: says otherwise
external: true with name:You, before upExactly the value of name:
No networks block at allCompose, on upThe project default network

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

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.

compose.yaml
# before
networks:
  shared-net:
    external: true

# after
networks:
  shared-net: {}

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.

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

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.

.github/workflows/ci.yml
docker compose down --remove-orphans   # not: docker system prune -f

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.

.github/workflows/ci.yml
- run: docker network inspect shared-net >/dev/null 2>&1 || docker network create shared-net
- run: docker compose up -d --wait

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.

compose.yaml
networks:
  shared-net:
    external: true
    name: tools_shared-net

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.

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.

Frequently asked questions

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.

Related guides

References

CI has no leftover state, and that is the point. Latchkey runners are $0.0025/min at 2 vCPU. Start free → 30-day trial · No credit card