Skip to content

Allow customizing the subdomain template for Preview Deployments #4283

Description

@FurkanTokmak

What problem will this feature address?

Preview Deployments currently generate subdomains using a fixed pattern that looks like preview-{appName}-{appHash}-{prHash}.{wildcardDomain}.

For example:

https://preview-my-app-a1b2c3-x9y8z7.example.com/
https://preview-my-app-a1b2c3-p4q5r6.example.com/

The two random hashes at the end make it impossible to tell at a glance which PR a preview belongs to. For teams reviewing multiple PRs at once, it would be useful to have preview URLs that encode meaningful context (PR number, branch name, etc.) so reviewers can identify previews at a glance and share links in PR comments.

Describe the solution you'd like

Allow users to define a template for the preview subdomain using variables, for example:

  • ${prNumber} — the pull request number (e.g. 123)
  • ${branchName} — a slugified branch name (e.g. feature-login)
  • ${appName} — the application name (e.g. my-app)
  • ${uniqueId} — the existing random hash

So a user could configure a template like:

pr-${prNumber}.example.com

and get pr-123.example.com for PR #123, instead of preview-my-app-a1b2c3-x9y8z7.example.com

I do not know how coolify does it but I know what I want here is possible there.

Describe alternatives you've considered

Manually assigning a domain to each preview after deployment — defeats the purpose of automatic preview deployments. And after rename, the rebuild needs to be triggered.

Additional context

I made an agent search through the codebase to validate that feature is not supported. The following analysis was made by AI:

I traced the full flow end-to-end to confirm this isn't already possible:

Subdomain generation — In packages/server/src/services/preview-deployment.ts the subdomain is hardcoded as:

const appName = `preview-${application.appName}-${generatePassword(6)}`;

Wildcard replacementgenerateWildcardDomain throws unless previewWildcard starts with *., then does a plain baseDomain.replace("*", hash). No ${...} variable interpolation.

DownstreamcreateDomain just .trim()s the host and inserts it; manageDomain reads domain.host and writes it verbatim into the Traefik router rule (only transform is toPunycode, a no-op for ASCII). Nothing mutates the host after it's written.

UI + validation — The settings form validates wildcardDomain as a plain z.string(), and db/validations/domain.ts only checks for non-empty/no-whitespace. No template parsing anywhere.

Will you send a PR to implement it?

Yes

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions