Registry authentication for Compose services (project-scoped)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 37.4k
- Forks
- 3k
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 73
Description
Data in examples is anonymized (registry.example.com, acme, servers dev / prod).
What problem will this feature address?
Compose services have no registry-authentication mechanism. A Compose deploy that pulls a private image relies entirely on a docker login that was run out of band on the target server, and there is no first-class, per-deploy way to establish it.
How it fails in practice
Setup (anonymized):
- One Compose service, image
registry.example.com/acme/wordpress:prodfrom a private registry. - Deployed to a remote
prodserver (not the Dokploy host). - Registry added in Settings → Registry with valid credentials.
Deploy log:
Image registry.example.com/acme/wordpress:prod Pulling
Image registry.example.com/acme/wordpress:prod Error
Head "https://registry.example.com/v2/acme/wordpress/manifests/prod": unauthorized: Authentication required
Error response from daemon: Head "...": unauthorized: Authentication required
Error: ❌ Docker command failed
Why it happens
getBuildComposeCommand (packages/server/src/utils/builders/compose.ts) emits no docker login step. The generated script runs docker compose up / docker stack deploy on the target server, and the pull authenticates only from that server's ~/.docker/config.json. DOCKER_CONFIG is injected into the env file but hardcoded to the shared /root/.docker.
The "Server (Optional)" field in the Settings → Registry dialog is the only path to seed that state. It:
- runs a one-off
docker loginon save (control-plane host if empty, orexecAsyncRemote(serverId, …)for a picked server); - is never persisted — no column on
registry,update/creatediscard it after use,registry.onedoesn't return it. On re-open the dropdown is always blank (<Select defaultValue>never reflects an async-loaded value either); - on Cloud, editing a registry without re-picking a server throws
"Select a server to add the registry"; - writes the shared
/root/.docker/config.jsonon the chosen host.
Contrast with Application (works)
Application authenticates per deploy:
- Source = Docker image →
buildRemoteDockerembedsdocker loginin the deploy script onbuildServerId || serverId; the same creds go to the Docker API on swarm service create (getAuthConfig). - Git build → push (multi-node swarm) →
getRegistryCommands(utils/cluster/upload.ts) embedsdocker login+tag+push. - Pull on other swarm nodes →
authConfigfrom the registry row.
Credentials travel with the deploy; nothing has to pre-exist on the host; it is self-healing. Compose has none of this.
Second problem: credential isolation on shared hosts
Because the seeding path writes the shared /root/.docker/config.json, a host that runs services from more than one project accumulates logins for every registry that was ever seeded on it. Any workload or shell on that host can then pull from all of them.
Concrete case: a DevOps operator manages several projects, each with its own private AWS ECR. If dev and prod (or a single shared box) get seeded for multiple projects' registries, project A's server holds project B's registry credentials in the shared Docker config. There is no per-project or per-service credential scoping.
Also affected / not affected
- Databases (
postgres,mysql,mariadb,mongo,redis) — schema has onlydockerImage, no registry/credential fields, and the deploy creates the service with noauthconfig. Private-registry images do not work for databases today at all. - Applications — unaffected; they already authenticate per deploy.
Describe the solution you'd like
Two parts. Part 1 is small and independent; part 2 is the feature.
Part 1 — fix the Settings → Registry "Server" field (small, no migration)
- Rewrite the label and description to say what it does: "Run
docker loginfor this registry on the selected servers now, to verify credentials / pre-authenticate." It is an action, not a stored association. - Fix
<Select defaultValue={field.value}>→value={field.value}and includeserverIdin theform.reset(...)branch so it stops rendering blank. - Position it next to Test Registry as a verification helper.
Part 2 — project-scoped registry authentication for Compose
Data model
- New
projectRegistryjoin table (projectId,registryId), many-to-many.registrystays organization-scoped; the association is explicit and cascade-deleted on project or registry delete. - No new columns on
registry. No per-server table. No change toapplications.
Deploy flow (Compose)
Before docker compose up / docker stack deploy, the generated script:
export DOCKER_CONFIG="<COMPOSE_PATH>/<appName>/.docker" # per-app, replaces shared /root/.docker
mkdir -p "$DOCKER_CONFIG"
# one login per registry linked to this service's project (fatal on failure):
printf %s "$R1_PASS" | docker login registry.example.com -u "$R1_USER" --password-stdin || exit 1
printf %s "$R2_PASS" | docker login 123456789012.dkr.ecr.eu-west-1.amazonaws.com -u "$R2_USER" --password-stdin || exit 1
docker compose -p <app> -f <file> up -d --build --remove-orphans
# or: docker stack deploy -c <file> <app> --prune --with-registry-auth
~/.docker/config.json(here, the per-app one) storesauthskeyed by registry hostname, so multiple logins coexist.docker compose/docker pullmatches each image's host to its entry automatically — no ordering concern.stack deploy --with-registry-authforwards the matching entries to swarm.
Result
- No dependency on out-of-band host state; self-healing like Application. A bad credential surfaces in the deploy log.
- Credentials for project A's registries land only in project A's services' isolated config dirs — a shared host never accumulates other projects' logins.
- Registries are set once per project, not per service.
- Two private registries in one Compose file → link both to the project.
Validation
- A project may link at most one registry per hostname (a second login to the same host would overwrite the first). Different ECR accounts have different hosts and are fine.
- Registry must belong to the same organization as the project.
Out of scope for the first PR (possible follow-ups): applying project registries to databases' authconfig; environment-level (dev/prod) scoping below project level; rewiring Applications to inherit project registries.
Describe alternatives you've considered
-
Bind registries to servers instead of projects. Rejected. Wrong granularity — "server X may use registry R" means every workload on X can, including services from another project that happen to deploy there. It also reintroduces a persistent host login and the need to track "is the login still valid" (stale on any external
config.jsonchange), which the per-deploy approach removes. -
A single
registryId/buildRegistryIdFK on the compose service (the approach in #5159 / #5224). Good direction and matches the Application pattern, but as the sole solution it is: single registry only; framed around the build server → push → pull flow, so the plain "privateimage:with no build server" case (the example above) is not covered; and it does not address the shared-/root/.dockerisolation problem (it adds cross-org validation, not per-project/per-service scoping). The project-scoped model can build on #5159 once merged, or replace the association layer. -
Do nothing — manual
docker loginon each server. The current workaround. Breaks silently whenever the host config is cleared or a token expires; does not scale to multiple servers or multiple registries. -
Auto-run
docker loginwhen a server is added. Rejected as too implicit — the new server may not have Docker ready, credentials may not apply, and failures happen silently in the background. An explicit link plus a visible "authenticate" action is clearer.
Additional context
Related issues / PRs
- #5224 (open) — feat(compose): remote build server and registry for prebuilt deploys. Requests build server + build registry on Compose, mirroring the app flow, with registry login on build and deploy hosts.
- #5159 (open, XL, currently CONFLICTING) — implementation of #5224. Adds
compose.buildServerId+compose.buildRegistryId(nullable FKs), migration0191, cross-org registry validation oncompose.update, server-delete guardrails. - #4148 (closed) — Docker Compose: Dedicated Build Server Support.
- #4150 / #4152 / #4046 (closed PRs) — earlier attempts at the same, not merged.
- #4804 (closed) — registry login "succeeds" but push fails on a remote/build server until a manual
docker loginon the VPS; same root cause (deploy-time auth on the right host). - #4518 (open) — after a restore, deploy fails until an extra save re-authenticates.
Nothing existing proposes project-scoped registry binding or per-app DOCKER_CONFIG isolation; both are new here. Neither covers the pure-pull Compose case or multi-registry-per-unit.
Migration
- Nothing stored needs migrating:
registrynever had a persistedserverId.registryrows and Application FKs are untouched. Migration = create the emptyprojectRegistrytable. - Non-breaking: a Compose service with no linked registry keeps the current behavior (pull from the host
~/.docker/config.json), so hosts already seeded manually keep working. Optional best-effort backfill: match image hostnames in a compose file against existing registries byregistryUrland pre-create links; skip if unreliable and show a hint on the service instead.
Caveat
docker stack deploy --with-registry-auth stores auth in the swarm raft log (encrypted, per-service). That path is inherently not ephemeral — it is a swarm mechanism, not a Dokploy issue, and applies regardless of this change.
Will you send a PR to implement it?
Maybe, need help — happy to implement, but would like to agree on the approach first (extend #5159's per-service FK vs. the project-scoped model here), and confirm whether Part 1 (the "Server" field fix) can land as its own small PR immediately.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with packages/server/src/utils/builders/compose.ts, then compare the Application authentication flow and review #5159 and #5224 before choosing the project-scoped approach. Part 1 also requires locating the Settings → Registry form. Done means Compose supports linked project registries with isolated per-app authentication, the projectRegistry migration exists, and the Server field behavior is corrected.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- docker, docker-compose, typescript
- Domain
- devops, infrastructure
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100