Skip to content
Latchkey

Compose "service ... refers to undefined secret" in CI

A service secret must reference a secret declared in the top-level secrets: block. If the name under the service has no matching top-level definition, Compose reports it as undefined and refuses to start.

What this error means

A compose command fails with "service \"app\" refers to undefined secret \"db_password\": invalid compose project".

compose
service "app" refers to undefined secret "db_password": invalid compose project

Diagnose it: build context, cache, or platform?

A Dockerfile that builds locally and fails in CI usually differs in one of three ways: the build context contains different files, the layer cache is cold or poisoned, or the runner architecture does not match what the base image provides.

Terminal
# what is actually being sent as build context (dockerignore applies)
docker build --no-cache --progress=plain -t probe . 2>&1 | head -40

# what platform are you on, and what does the base image support?
docker version --format '{{.Server.Arch}}'
docker buildx imagetools inspect <base-image> | grep -i platform

# prove it is not a cache artefact
docker build --no-cache .

Common causes

No matching top-level secrets entry

The service lists a secret name that is never declared under the top-level secrets: key.

A name mismatch between service and top level

The service references one name while the top-level block declares a slightly different one, so resolution fails.

How to fix it

Declare the secret at the top level

  1. Add a top-level secrets: entry whose key matches the service reference.
  2. Point it at a file or environment source.
  3. Re-run; the secret now resolves.
docker-compose.yml
services:
  app:
    secrets:
      - db_password
secrets:
  db_password:
    file: ./secrets/db_password.txt

Provide the secret source in CI

Write the secret file from a CI secret before running compose so the file-based source exists.

.github/workflows/ci.yml
run: |
  mkdir -p secrets
  printf '%s' "${{ secrets.DB_PASSWORD }}" > secrets/db_password.txt
  docker compose up -d

Keep the build context small and deterministic

  • A missing .dockerignore sends node_modules, .git, and build output to the daemon, which is slow and can change layer hashes between environments.
  • A COPY of a path that exists locally but is gitignored will fail in CI, because the runner only has what the checkout produced.
  • Multi-arch builds need buildx and QEMU set up explicitly; a plain docker build on an ARM runner silently produces an ARM image.

How to prevent it

  • Declare every referenced secret in the top-level secrets: block.
  • Keep service and top-level secret names identical.
  • Materialize secret source files in CI before compose runs.

Frequently asked questions

What causes Compose "service ... refers to undefined secret" in CI?
There are 2 common causes: no matching top-level secrets entry and a name mismatch between service and top level. The service lists a secret name that is never declared under the top-level secrets: key.
How do I fix Compose "service ... refers to undefined secret" in CI?
There are 2 fixes depending on which cause you have: declare the secret at the top level and provide the secret source in ci. Work through them in order, since the first is the most common.
What does Compose "service ... refers to undefined secret" in CI actually mean?
A compose command fails with "service \"app\" refers to undefined secret \"db_password\": invalid compose project".
How do I stop Compose "service ... refers to undefined secret" in CI happening again?
Declare every referenced secret in the top-level secrets: block. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card