Dokploy / Dokploy/dokploy

Registry authentication for Compose services (project-scoped)

Open
#5,366 1 comment 0 reactions 0 assignees View on GitHub

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:prod from a private registry.
  • Deployed to a remote prod server (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 login on save (control-plane host if empty, or execAsyncRemote(serverId, …) for a picked server);
  • is never persisted — no column on registry, update/create discard it after use, registry.one doesn'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.json on the chosen host.
Contrast with Application (works)

Application authenticates per deploy:

  • Source = Docker image → buildRemoteDocker embeds docker login in the deploy script on buildServerId || 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) embeds docker login + tag + push.
  • Pull on other swarm nodes → authConfig from 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 only dockerImage, no registry/credential fields, and the deploy creates the service with no authconfig. 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 login for 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 include serverId in the form.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 projectRegistry join table (projectId, registryId), many-to-many. registry stays 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 to applications.

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) stores auths keyed by registry hostname, so multiple logins coexist. docker compose / docker pull matches each image's host to its entry automatically — no ordering concern.
  • stack deploy --with-registry-auth forwards 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.json change), which the per-deploy approach removes.

  • A single registryId / buildRegistryId FK 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 "private image: with no build server" case (the example above) is not covered; and it does not address the shared-/root/.docker isolation 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 login on 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 login when 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), migration 0191, cross-org registry validation on compose.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 login on 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: registry never had a persisted serverId. registry rows and Application FKs are untouched. Migration = create the empty projectRegistry table.
  • 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 by registryUrl and 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.