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".
service "app" refers to undefined secret "db_password": invalid compose projectDiagnose 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.
# 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
- Add a top-level
secrets:entry whose key matches the service reference. - Point it at a file or environment source.
- Re-run; the secret now resolves.
services:
app:
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txtProvide the secret source in CI
Write the secret file from a CI secret before running compose so the file-based source exists.
run: |
mkdir -p secrets
printf '%s' "${{ secrets.DB_PASSWORD }}" > secrets/db_password.txt
docker compose up -dKeep the build context small and deterministic
- A missing
.dockerignoresendsnode_modules,.git, and build output to the daemon, which is slow and can change layer hashes between environments. - A
COPYof 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
buildxand QEMU set up explicitly; a plaindocker buildon 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.