Skip to content

feat(preview): add Gitea/Forgejo support for preview deployments - #5149

Open
ankit8697 wants to merge 2 commits into
Dokploy:canaryfrom
ankit8697:claude/dokploy-issue-3828-omx8un
Open

feat(preview): add Gitea/Forgejo support for preview deployments#5149
ankit8697 wants to merge 2 commits into
Dokploy:canaryfrom
ankit8697:claude/dokploy-issue-3828-omx8un

Conversation

@ankit8697

@ankit8697 ankit8697 commented Aug 21, 2026

Copy link
Copy Markdown

What is this PR about?

Preview deployments are currently GitHub-only: pages/api/deploy/github.ts is the only handler that reacts to pull_request events, createPreviewDeployment refuses to run without an application.githubId and talks to octokit directly, and deployPreviewApplication only clones when sourceType === "github" — for any other source type it skips the build entirely yet still posts a ✅ "success" comment on the pull request. This PR makes preview deployments work for Gitea and Forgejo repositories (Forgejo's API is Gitea-compatible, so the existing "Gitea" provider covers both). Following the first option suggested in #3828, pull request events are handled on the deployment webhook that already exists per application (/api/deploy/{refreshToken}) rather than on a new one, so there is no migration and users only have to tick the pull request events on the webhook they already created for push auto-deploy. Behind that, the preview lifecycle gains a small provider-agnostic layer for pull request comments and permission checks; the GitHub branches of it delegate to the existing functions, so GitHub behaviour is unchanged.

Checklist

Before submitting this PR, please make sure that:

Issues related (if applicable)

closes #3828

Screenshots (if applicable)

The only UI change is a provider-aware icon on the "Pull Request" link and an info block on the Preview Deployments tab explaining which Gitea webhook events previews need:

Gitea / Forgejo: preview deployments are driven by the webhook you added for this application (its URL is shown in the Deployments tab). In the repository webhook settings, choose Custom Events and enable Pull Request and Pull Request Synchronized — without the latter, previews are created but never updated when new commits are pushed. Enable Pull Request Label as well if you use the preview labels filter.

That detail is not cosmetic: Gitea exposes those as separate checkboxes, and enabling only "Pull Request" is the failure mode where previews appear but never refresh.


What changed, file by file

Gitea REST helperspackages/server/src/utils/providers/gitea.ts

  • giteaApiRequest, an authenticated fetch wrapper that resolves giteaInternalUrl ?? giteaUrl and refreshes the token first.
  • Issue comment create / update / get / list, and checkGiteaUserRepositoryPermissions.
  • These read the token through findGiteaById, not application.gitea, because findApplicationById redacts accessToken.

Provider-agnostic preview commentspackages/server/src/services/preview-comment.ts (new)

  • getPreviewCommentContext(application) resolves the pull request coordinates for a GitHub or Gitea application, and returns null for source types that cannot host previews.
  • createPreviewComment / updatePreviewComment / ensurePreviewComment / createPreviewSecurityBlockedComment / checkPreviewAuthorPermissions dispatch on the provider inside the function body (not via a module-level table, because of the existing github.tspreview-deployment.ts import relationship).

Preview lifecycleservices/preview-deployment.ts, services/application.ts

  • createPreviewDeployment no longer hard-requires githubId.
  • deployPreviewApplication and rebuildPreviewApplication write their status comment through the dispatch layer, and deployPreviewApplication clones Gitea repositories.
  • createPreviewDeploymentComment became unused once the callers moved to ensurePreviewComment, so it is removed — which also drops the github.tspreview-deployment.ts import cycle.

Webhook handlingapps/dokploy/server/utils/gitea-preview.ts (new), wired into pages/api/deploy/[refreshToken].ts

  • Pull request deliveries are handled before the autoDeploy gate, because previews are a separate feature from push auto-deploy and must work with auto-deploy off.
  • Gitea's action names are mapped onto the existing behaviour: synchronized (not GitHub's synchronize), and label_updated / label_cleared instead of labeled / unlabeled. The collaborator check, the previewLabels filter and the previewLimit cap (still applied only to new previews, per 98dbc59) all carry over.
  • Routing is an exact comparison on X-Gitea-Event / X-Forgejo-Event. Gitea folds every pull request sub-event into the single name pull_request and keeps the specific one in X-Gitea-Event-Type, so the generic header alone is sufficient. The GitHub compatibility headers Gitea also sends are deliberately ignored so GitHub deliveries keep taking the existing path.

Drive-by fixes in the code being touched

  • deployPreviewApplication now throws for source types it cannot build, instead of reporting success without building.
  • cloneGiteaRepository uses its (previously dead) getErrorCloneRequirements guard and checks the access token, so a half-configured provider gets a clear error instead of git clone .../null/null.git.
  • A failing status comment on the error path can no longer mask the real build error.
Hardening beyond parity with the GitHub handler

The GitHub handler derives its application set from the payload (repository, owner, branch, githubId are all in the where). Here the application comes from the URL, so the payload needs checking:

  • Repository identityrepository.name and the repository owner must match giteaRepository / giteaOwner, case-insensitively. Without this, a webhook on any repository could deploy an arbitrary branch of the configured one.
  • Fork pull requests are skipped with an explicit message; cloneGiteaRepository always clones the configured repository, so a fork-only branch would just produce a failing build.
  • closed cleanup is scoped to this application. Gitea pull request ids are per-instance auto-increments (a fresh test instance issued id 1), so the installation-wide findPreviewDeploymentsByPullRequestId lookup that is safe for GitHub — whose ids are globally unique — would collide across two Gitea instances.
  • Repository owners short-circuit the permission lookup. Gitea only answers /collaborators/{u}/permission for site admins, repository admins, or users asking about themselves; everyone else gets a 403. A 403 is therefore reported as unverified and skips the deployment without posting the "you lack access" comment, because it means the Dokploy-side account lacks repository admin, not that the author is untrusted.
  • owner is allow-listed alongside write and admin. Gitea returns owner as a distinct role and has no maintain level, so the "Required Level" line in the blocked-deployment comment is now per-provider.
How this was verified, and what is still untested

pnpm typecheck, pnpm build and pnpm test all pass locally — the same three jobs pull-request.yml runs. 40 new tests pass alongside the existing suite; overall 908 pass, and the only 5 failures (application.real.test.ts, env-file-literals.test.ts) reproduce identically on a clean canary checkout because they need real nixpacks/Docker.

Rather than rely on the API docs, I stood up a real Gitea 1.24.3 instance (SQLite; four users: repository owner, write collaborator, read collaborator, outsider), opened a real pull request as the write collaborator, pushed a second commit to it, added a label, cleared the labels, and pointed a repository webhook at a capture server.

Check Result
X-Gitea-Event for opened / sync / label add / label clear pull_request in all four cases; the specific type in X-Gitea-Event-Type
Action names opened, synchronized, label_updated, label_cleared
Payload fields (pull_request.id/number/title/html_url/user.login/base.ref/head.ref/head.sha/head.repo.owner.login, repository.name/owner.login) all present as assumed
Issue-comment create / get / update / list through the new helpers 201 / 200 / 200 / 200
/collaborators/{u}/permission owner for the repository owner, plus write and read; 403 when the connected account is not a repository admin
Issue-comment write with an OAuth token issued using Dokploy's current scope string 201 — existing Gitea providers need no re-authorization

Two things that testing corrected, both fixed here and covered by fixtures captured from that instance (__test__/deploy/fixtures/gitea-pull-request-deliveries.json):

  1. Comments on a pull request arrive as X-Gitea-Event: issue_comment with X-Gitea-Event-Type: pull_request_comment. Dokploy posts preview status comments itself, so those deliveries come straight back to the same webhook. The exact match on the generic event name rejects them correctly; a prefix match on the event type, which I had considered, would have routed them into the pull request handler.
  2. On a public repository Gitea reports a non-collaborator as read, not 404. Harmless for behaviour — still blocked, with an accurate comment — but a code comment claimed otherwise.

Still untested: a full end-to-end preview build and deploy from a running Dokploy instance, which needs Postgres, Redis, Docker Swarm, Traefik and wildcard DNS I could not stand up. The Gitea-specific surface this PR changes is covered above; the build and deploy machinery downstream of it is provider-independent and untouched apart from the one new cloneGiteaRepository call, whose emitted command is unit-tested. Worth one real end-to-end pass before merging.

Other notes for reviewers
  • No signature verification, deliberately. Gitea sends X-Gitea-Signature and X-Hub-Signature-256 on every delivery (the test instance sent both even with no secret configured), and it is tempting to verify one against the refreshToken. Verifying only when the header is present buys nothing — an attacker holding the token simply omits it — and making it mandatory would break every existing push webhook whose secret is something else. The trust model for this endpoint is unchanged: the refreshToken in the URL is the shared secret, and it is rotatable from the UI. Anyone who needs real HMAC verification wants a provider-level Gitea webhook with a stored secret, which the preview-comment.ts abstraction makes cheap to add later.
  • One repository mapped to N applications needs N webhooks, since the URL is per-application. That is already true for push auto-deploy on Gitea.
  • The custom git source type pointing at a Gitea instance stays unsupported — there is no giteaId to authenticate comment writes with. Such a delivery now returns a message saying so, rather than a bare "Branch Not Match".
  • openapi.json needs no regeneration (no new tRPC procedures), no audit-log entry is added (webhooks do not audit today for any provider), and the preview components contain no t() calls, so there are no i18n keys to add.
  • Docs live in Dokploy/website and need a follow-up note that previews support GitHub and Gitea/Forgejo, including which webhook events to enable. Happy to open that PR too.

Greptile Summary

This PR adds Gitea and Forgejo preview-deployment support through application deployment webhooks and introduces provider-neutral preview comments and permission checks.

  • Routes Gitea/Forgejo pull-request events into preview creation, refresh, and cleanup.
  • Adds Gitea REST helpers for comments and collaborator permissions.
  • Extends preview cloning, status comments, tests, and provider-aware UI guidance.

Confidence Score: 4/5

The PR should not merge until clearing required Gitea preview labels no longer triggers a fresh deployment of the existing preview.

A captured Gitea label_cleared payload retains the former label, so the new handler passes its label filter and queues an existing preview even though that label has just been removed.

Files Needing Attention: apps/dokploy/server/utils/gitea-preview.ts

Reviews (1): Last reviewed commit: "test(preview): verify gitea preview webh..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Context used:

claude added 2 commits August 20, 2026 18:02
Preview deployments were wired exclusively to GitHub: only
`/api/deploy/github` reacted to `pull_request` events,
`createPreviewDeployment` refused to run without an `application.githubId`
and talked to octokit directly, and `deployPreviewApplication` only cloned
when `sourceType === "github"` - silently reporting success without
building for every other source type.

Gitea/Forgejo repositories now get the same feature, driven by the
per-application webhook that already exists for push auto deployments
(`/api/deploy/{refreshToken}`), so users only have to enable the pull
request events on the webhook they already created.

- Add the Gitea REST helpers preview deployments need: issue comment
  create/update/get/list and the collaborator permission lookup, all
  going through `findGiteaById` because `findApplicationById` redacts the
  access token.
- Introduce `services/preview-comment.ts`, a provider-agnostic layer that
  resolves the pull request coordinates of an application and dispatches
  comment and permission calls to GitHub or Gitea. The GitHub branches
  delegate to the existing functions, so GitHub behaviour is unchanged.
- Teach `createPreviewDeployment`, `deployPreviewApplication` and
  `rebuildPreviewApplication` to use that layer, and clone Gitea
  repositories for previews.
- Handle Gitea/Forgejo `pull_request` deliveries before the `autoDeploy`
  gate, mapping Gitea's action names (`synchronized`, `label_updated`,
  `label_cleared`) onto the existing create/redeploy/remove behaviour and
  preserving the collaborator check, preview labels and preview limit.
- Validate that the payload repository matches the one the application is
  configured for, skip pull requests from forks, and short circuit the
  permission lookup for the repository owner (Gitea only answers that
  endpoint for repository admins).
- Fail explicitly instead of reporting a successful preview deployment for
  source types that cannot build one, and guard the Gitea clone against a
  missing owner, repository, branch or access token.
- Show the icon of the configured provider on the pull request link and
  explain which Gitea webhook events previews need.
Ran the handler against webhook deliveries captured from a live Gitea
1.24.3 instance (pull request opened by a write collaborator, a second
commit pushed to it, a label added, then all labels cleared, plus a
comment on the same pull request) and added those payloads as fixtures.

Two things the live run corrected:

- Gitea sends pull request *comment* deliveries as
  `X-Gitea-Event: issue_comment` with `X-Gitea-Event-Type:
  pull_request_comment`. Dokploy posts preview status comments itself, so
  those deliveries come straight back to the same webhook and must not be
  treated as pull request events - which the exact match on the generic
  event name already does, and a prefix match on the event type would not.
- On a public repository Gitea reports a non-collaborator as `read`, not as
  a 404, so the comment claiming otherwise was wrong.

Also confirmed live: `owner` is returned as a distinct permission for the
repository owner, and the permission endpoint answers 403 when the
connected account is not a repository admin, which is the case the handler
reports as unverified instead of blaming the pull request author.
@ankit8697
ankit8697 requested a review from Siumauricio as a code owner August 21, 2026 07:39
@dosubot dosubot Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Aug 21, 2026
* Actions that refresh an existing preview but never create one - the GitHub
* handler treats `unlabeled` the same way.
*/
const UPDATE_ONLY_ACTIONS = ["label_cleared"];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Cleared labels trigger redeployment

When a Gitea pull request with an existing preview loses all labels, the label_cleared payload still contains the previous label, so this update-only action passes the configured-label check and queues a new deployment for an ineligible pull request.

Knowledge Base Used: Application Deployment Flow

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

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Gitea/Forgejo support for Preview Deployments

2 participants