Skip to content

feat(compose): support registry images and remote prebuilt deploys - #5159

Open
designly1 wants to merge 6 commits into
Dokploy:canaryfrom
designly1:feature/compose-registry
Open

feat(compose): support registry images and remote prebuilt deploys#5159
designly1 wants to merge 6 commits into
Dokploy:canaryfrom
designly1:feature/compose-registry

Conversation

@designly1

@designly1 designly1 commented Aug 22, 2026

Copy link
Copy Markdown

Closes #5224

Summary

  • Adds optional build server and build registry settings to Compose stacks, mirroring the application remote-build flow: images are built and pushed on the build server, then the deploy server pulls prebuilt images instead of building locally.
  • Introduces a prebuilt deploy path (pull + up --no-build) when a build server is configured, with registry login on both build and deploy hosts.
  • Fixes split build/deploy logging so deploy-phase logs are written on the deploy server (including log directory creation and error-path log aggregation) and corrects chained prebuilt compose commands to prefix follow-up segments with docker compose.

Demo

https://cdn.designly.biz/video/dokploy-test-demo.mp4

Hardening

This feature introduced a second FK from compose to server (buildServerId, alongside existing serverId). Several server-management paths were updated to handle that safely:

Drizzle relation disambiguation

composeRelations already named its two server links (composeServer, composeBuildServer), but serverRelations still had an unnamed compose: many(compose). Any query that loaded a server with with: { compose: ... } — including remote server delete — failed with:

There are multiple relations between "compose" and "server". Please specify relation name

Fix: mirror the application pattern on the server side:

  • composerelationName: "composeServer" (deploy target)
  • buildComposerelationName: "composeBuildServer" (build host)

Server delete guardrails

haveActiveServices() now counts both deploy-target and build-server references before allowing deletion:

  • applications + buildApplications
  • compose + buildCompose

Previously only the deploy-target side was checked. A server used only as a build host could appear deletable (totalSum === 0 in the UI) even while compose stacks still referenced it via buildServerId.

Registry authorization (compose update)

compose.update now requires the target compose to belong to the active organization (matching one/delete) before any fields are persisted. Registry and build-server IDs are then validated against the same active org. This closes the cross-org credential association Greptile flagged when accessedServices spans organizations.

Split build/deploy log cleanup and read path

  • Cleanup: when buildServerId is set, deployment log directories are removed from the build server on cleanup, preventing orphaned log files after split build/deploy runs.
  • Read path: deployment.readLogs now resolves the log host as deployment.buildServerId || deployment.serverId || …, matching where createDeploymentCompose writes the main log file on split build/deploy runs.

Database migration

  • 0188_sharp_arclight.sql adds compose.buildServerId and compose.buildRegistryId (nullable FKs to server and registry).
  • Rebased onto latest canary; upstream already had 0187_grant_terminal_permission_to_read_roles.

Documentation

Companion docs PR: Dokploy/website#178

Test plan

  • Run migration on a fresh/canary instance and confirm Compose settings show Build Server + Build Registry fields
  • Deploy a compose stack with build directives on the same server (no build server) — behavior unchanged
  • Deploy a compose stack with build server + registry on a different deploy server — build/push succeeds on build host, deploy host pulls and starts without --build
  • Confirm deployment logs include both build and deploy phases when servers differ
  • Verify failed deploys still surface deploy-phase logs in the main deployment log
  • Delete a remote server with no services — succeeds (no Drizzle relation error)
  • Delete a remote server referenced as a compose build server — blocked with "Server has active services"
  • Attempt compose update on a foreign-org service — rejected before registry is persisted
  • Read deployment logs via API for a split build/deploy compose run — returns build-server log content
  • Run pnpm test __test__/compose/create-command-prebuilt.test.ts
  • Run pnpm test __test__/compose/build-compose-command.test.ts
  • Run pnpm test __test__/compose/build-registry-images.test.ts

Validated on gpc3 (ghcr.io/designly1/dokploy:local).

@designly1
designly1 requested a review from Siumauricio as a code owner August 22, 2026 03:41
@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Aug 22, 2026
Comment thread apps/dokploy/server/api/routers/compose.ts
Comment thread packages/server/src/services/deployment.ts
@designly1

Copy link
Copy Markdown
Author

Thanks @greptile-apps — both P1s were valid and are fixed in 38bcee3:

  1. Unscoped build registry credentialscompose.update now verifies buildRegistryId belongs to the active organization before persisting (same pattern as the registry router).
  2. Build-server logs escape cleanupremoveDeployment and removeDeploymentsByComposeId now delete logs via buildServerId || serverId, matching where create writes them.

Note: applications already have a similar buildRegistryId update ACL gap and related log-host cleanup quirks; leaving that as a separate follow-up since it predates this PR.

@greptile-apps

greptile-apps Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Both fixes look correct.

Registry ACL (compose.ts) — the update handler now fetches the registry and gates on reg.organizationId !== ctx.session.activeOrganizationId before persisting, exactly matching the pattern used elsewhere. No gaps visible.

Log cleanup (deployment.ts) — both removeDeploymentsByComposeId and removeDeployment now resolve logServerId = deployment.buildServerId || deployment.serverId (or the compose equivalent) and route the rm to that host. createDeploymentCompose was already writing to buildServerId || serverId, so read and delete now use the same host.

The pre-existing application gap note is fair — it predates this PR and is a separate surface. Both P1s here are properly addressed.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

Comment thread apps/dokploy/server/api/routers/compose.ts
@designly1

Copy link
Copy Markdown
Author

Re-submitted with an additional hardening commit (a662f163a).

What changed since last review:

  • Fixed remote server delete failing with Drizzle's ambiguous composeserver relation error after buildServerId was added
  • Named server-side relations (compose / buildCompose) to match the compose schema
  • Extended haveActiveServices() to block deletion when a server is referenced as a build host (compose or application)

PR description updated with a Hardening section covering relation disambiguation, delete guardrails, registry org validation, and split build/deploy log cleanup.

Validated on gpc3 (ghcr.io/designly1/dokploy:local).

Comment thread packages/server/src/services/deployment.ts
@designly1

Copy link
Copy Markdown
Author

Re-submitted with Greptile follow-up fixes in e74ffde28.

Addressed since last review:

  1. Cross-tenant registry associationcompose.update now loads the compose and rejects updates when the service is not in the active organization, before persisting buildRegistryId or other fields (same guard as one/delete).
  2. Split logs read from wrong serverdeployment.readLogs now prefers deployment.buildServerId when resolving the remote host, matching where split compose deployments write the main log file.

Prior hardening commits remain: server-relation disambiguation (a662f163a), registry org validation + log cleanup (38bcee359), split deploy logging (12a35e133).

Please re-review.

@designly1
designly1 force-pushed the feature/compose-registry branch from e74ffde to 4f73324 Compare August 28, 2026 03:47
designly1 and others added 5 commits August 29, 2026 21:54
Adds compose registry configuration, build pipeline updates, and migration for stored image metadata.

Co-authored-by: Cursor <cursoragent@cursor.com>
Ensure deploy log directories are created on the deploy server when build
and deploy run on different hosts, append deploy logs on failure, and
prefix chained prebuilt compose commands with `docker compose`.
Move onlyBuiltDependencies to pnpm-workspace.yaml per pnpm v10 convention.

Co-authored-by: Cursor <cursoragent@cursor.com>
Reject buildRegistryId from other organizations on compose update, and
delete deployment logs from buildServerId when present so split
build/deploy stacks do not leave orphaned files.

Co-authored-by: Cursor <cursoragent@cursor.com>
Name server-side compose relations to match compose.buildServerId and
count both deploy-target and build-server references in haveActiveServices
so remote server deletion no longer fails with ambiguous Drizzle relations.

Co-authored-by: Cursor <cursoragent@cursor.com>
Require compose updates to target a service in the active organization
before persisting build registry credentials, and read deployment logs from
buildServerId when split compose builds write logs on the build host.

Co-authored-by: Cursor <cursoragent@cursor.com>
@designly1
designly1 force-pushed the feature/compose-registry branch from 4f73324 to e186b91 Compare August 30, 2026 02:54
@designly1

Copy link
Copy Markdown
Author

Rebased onto latest canary and resolved the Drizzle migration conflict.

Migration fix: upstream already had 0187_grant_terminal_permission_to_read_roles, so the compose registry migration was renumbered to 0188_sharp_arclight (SQL, snapshot, and journal updated; snapshot prevId chain fixed).

Branch is now merge-clean against canary. Please re-review when you have a chance — @Siumauricio

@designly1

Copy link
Copy Markdown
Author

Follow-up to get this merge-ready per contributing guidelines:

@Siumauricio — ready for review when you have a chance.

Still optional: a short screen recording of the Compose Advanced → Build Server flow would help reviewers (validated manually on gpc3, no recording attached yet).

@designly1

Copy link
Copy Markdown
Author

Demo video (Compose build server + registry, split build/deploy on gpc3): https://cdn.designly.biz/video/dokploy-test-demo.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(compose): remote build server and registry for prebuilt deploys

1 participant