Skip to content

feat(preview): deterministic per-PR preview domains via ${prNumber} template variable#4766

Open
fidelis05 wants to merge 2 commits into
Dokploy:canaryfrom
fidelis05:feat/preview-pr-number-template
Open

feat(preview): deterministic per-PR preview domains via ${prNumber} template variable#4766
fidelis05 wants to merge 2 commits into
Dokploy:canaryfrom
fidelis05:feat/preview-pr-number-template

Conversation

@fidelis05

@fidelis05 fidelis05 commented Jul 8, 2026

Copy link
Copy Markdown

What is this PR about?

Adds a ${prNumber} template variable to the preview deployments Wildcard Domain setting, and a matching ${{preview.prNumber}} token for preview env / build args / build secrets.

Motivation: when one repo drives two Dokploy applications (e.g. a frontend and a backend), a PR triggers a preview deployment for each — but today's preview domains always contain a random suffix, so neither service can know the other's URL in advance. With this PR, both apps can use deterministic per-PR patterns and cross-reference each other before either is deployed:

  • Frontend wildcard domain: frontend-pr${prNumber}.example.com
  • Backend wildcard domain: backend-pr${prNumber}.example.com
  • Frontend previewEnv: VITE_API_URL=https://backend-pr${{preview.prNumber}}.example.com
  • Backend previewEnv: FRONTEND_URL=https://frontend-pr${{preview.prNumber}}.example.com

How

Two self-contained commits:

  1. ${prNumber} in the wildcard domaingenerateWildcardDomain in packages/server/src/services/preview-deployment.ts now substitutes ${prNumber} (from the already-stored pullRequestNumber) before the existing * logic. If no * remains, the domain is returned as-is (fully deterministic); otherwise the legacy */sslip.io hash path runs unchanged, so *.pr-${prNumber}.example.com combines per-PR scoping with per-app uniqueness. A domain with neither * nor ${prNumber} now throws, since every PR would collide on the same host. The function is renamed to generatePreviewWildcardDomain and exported for testing — the old name collided with an unrelated generateWildcardDomain in services/domain.ts through the barrel export.
  2. ${{preview.prNumber}} in preview env — resolved in deployPreviewApplication / rebuildPreviewApplication over previewEnv, previewBuildArgs and previewBuildSecrets, following the existing ${{project.*}} / ${{environment.*}} double-brace convention. Kept out of the shared prepareEnvironmentVariables resolver on purpose, since that function also serves non-preview deployments, compose and databases.

Existing setups are unaffected: with no ${prNumber} in the pattern, behavior is byte-for-byte identical to before (default *.sslip.io included). No schema changes or migrations.

Known trade-off

If two applications are given the same ${prNumber}-only pattern, their previews for a given PR would collide on one host — previously impossible because every generated domain embedded the app's own name. The UI tooltip warns about this and recommends per-app prefixes (or *.pr-${prNumber}.… which keeps the app hash). A server-side duplicate-host check in createPreviewDeployment would be a good follow-up; happy to add it here if preferred.

Relationship to PR #4667

Complementary, not overlapping: #4667's ${{service.<name>.fqdn}} resolves a sibling's applicationId-linked domains, and preview domains are only linked via previewDeploymentId — so it resolves production URLs, and structurally can't reach the PR-scoped preview case. This PR covers that case via deterministic domains instead of lookup.

Checklist

Before submitting this PR, please make sure that:

  • You created a dedicated branch based on the canary branch.
  • You have read the suggestions in the CONTRIBUTING.md file https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#pull-request
  • You have tested this PR in your local instance. If you have not tested it yet, please do so before submitting. This helps avoid wasting maintainers' time reviewing code that has not been verified by you.

Testing done so far: 9 new unit tests for generatePreviewWildcardDomain (substitution, combined *.pr-${prNumber} form, two-app distinctness, legacy */sslip.io paths unchanged, both validation throws) — all passing, plus the existing env suite (68/68) and clean typechecks on @dokploy/server and apps/dokploy. A live end-to-end run via a real GitHub PR webhook is still pending, hence the unchecked box.

Issues related (if applicable)

Addresses the cross-service preview URL need described in #2028 (the "alternatives considered" scenario) and #2743, for application-type services. Does not close either: #2028's primary ask is compose preview support, and #2743 asks for per-preview env overrides in the UI.

fidelis05 and others added 2 commits July 8, 2026 02:24
Preview deployments generate their domain by substituting the app's
"Wildcard Domain" setting. Previously the only supported token was the
"*" wildcard, which is replaced with a hash that always includes a
random suffix, so the resulting domain is not predictable ahead of time.

Add a ${prNumber} template variable, already available from the pull
request that triggers the deployment, so users can build a domain that
is deterministic per PR (e.g. frontend-pr${prNumber}.example.com). This
lets a sibling service in the same PR reference this app's preview URL
before it has been deployed. ${prNumber} can be combined with "*" to
keep per-app uniqueness (e.g. *.pr-${prNumber}.example.com).

The wildcard generator is renamed to generatePreviewWildcardDomain and
exported (it previously collided with an unrelated generateWildcardDomain
in services/domain.ts) so its pure substitution logic can be unit tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… args

When a preview deployment is built, previewEnv / previewBuildArgs /
previewBuildSecrets are copied onto the application before the build
runs. Add resolution of a ${{preview.prNumber}} token in those three
strings, matching the existing ${{project.*}} / ${{environment.*}}
double-brace convention.

Combined with the ${prNumber} wildcard-domain support, this lets one
service point at another's deterministic preview URL for the same PR,
e.g. VITE_API_URL=https://backend-pr${{preview.prNumber}}.example.com
on a frontend, without knowing the URL in advance.

The substitution is applied in deployPreviewApplication /
rebuildPreviewApplication before prepareEnvironmentVariables runs, rather
than extending that shared resolver, since prepareEnvironmentVariables is
also used by non-preview deployments, compose and databases and does not
know this preview-only token.
@fidelis05 fidelis05 requested a review from Siumauricio as a code owner July 8, 2026 05:34
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant