feat(github): add tag pattern filtering for deployments#3726
feat(github): add tag pattern filtering for deployments#3726Alm0stEthical wants to merge 4 commits intoDokploy:canaryfrom
Conversation
Add ability to filter which tags trigger deployments using glob patterns. Users can now specify patterns like "v*", "release-*" to selectively deploy. Changes: - Add tagPatterns field to application and compose schemas - Add getGithubTags API endpoint to fetch repository tags - Implement pattern matching in webhook handler using micromatch - Add Tag Patterns multi-select UI with creatable input - Backwards compatible: empty patterns = deploy on any tag 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…d safety improvements - Extract TagPatternsField into a shared component to fix React hooks anti-pattern (useState was being called inside render callback which violates Rules of Hooks) - Remove ~270 lines of duplicate code between application and compose providers - Add tagPatterns field to test fixtures (drop.test.ts, traefik.test.ts) - Add try-catch around micromatch.isMatch() for defensive error handling 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1) UI: Trim and ignore empty input when adding a tag pattern in TagPatternsField to avoid creating blank patterns. 2) API (pages/api/deploy/github.ts): Introduce deployedAppsCount and deployedComposeCount counters, increment them when scheduling/dispatching deployments, and compute totalApps from these counters. Update the empty-result message to indicate no matching apps or patterns. 3) Server (server/api/routers/github.ts): Fix authorization logic to require both matching organizationId and userId for the GitHub provider; the previous conditional used incorrect boolean logic that could allow unauthorized access.
There was a problem hiding this comment.
Pull request overview
Adds tag-based deployment filtering for GitHub providers by allowing apps/compose services to specify glob patterns that must match a pushed tag before deploying. This extends both backend schema/API and the dashboard UI to configure and fetch tags.
Changes:
- Add
tagPatternstoapplicationandcomposeschemas + DB migration. - Add GitHub tags listing endpoint (
getGithubTags) for UI assistance. - Apply
micromatchfiltering in the GitHub webhook tag-deploy path; add UI field for selecting/entering patterns.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/server/src/utils/providers/github.ts | Adds getGithubTags helper that paginates repos.listTags. |
| packages/server/src/db/schema/github.ts | Introduces apiFindGithubTags input schema for the tags endpoint. |
| packages/server/src/db/schema/compose.ts | Adds tagPatterns column to compose table schema. |
| packages/server/src/db/schema/application.ts | Adds tagPatterns column to application table schema + includes it in GitHub provider save schema. |
| apps/dokploy/server/api/routers/github.ts | Adds getGithubTags protected TRPC procedure. |
| apps/dokploy/server/api/routers/application.ts | Persists tagPatterns when saving GitHub provider settings. |
| apps/dokploy/pages/api/deploy/github.ts | Filters tag-triggered deployments by tagPatterns using micromatch. |
| apps/dokploy/package.json | Dependency ordering changes (no functional change intended). |
| apps/dokploy/drizzle/meta/_journal.json | Adds migration journal entry for 0139. |
| apps/dokploy/drizzle/meta/0139_snapshot.json | New drizzle snapshot reflecting tagPatterns columns. |
| apps/dokploy/drizzle/meta/0134_snapshot.json | Updates existing historical snapshot (needs correction). |
| apps/dokploy/drizzle/0139_volatile_doomsday.sql | Adds tagPatterns columns via SQL migration. |
| apps/dokploy/components/dashboard/shared/tag-patterns-field.tsx | New UI component to manage tag patterns/tags selection. |
| apps/dokploy/components/dashboard/compose/general/generic/save-github-provider-compose.tsx | Adds tag-patterns field + tag fetching when trigger type is tag. |
| apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx | Adds tag-patterns field + tag fetching when trigger type is tag. |
| apps/dokploy/test/traefik/traefik.test.ts | Updates test fixture to include tagPatterns. |
| apps/dokploy/test/drop/drop.test.ts | Updates test fixture to include tagPatterns. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { | ||
| if (e.key === "Enter" && inputValue.trim()) { | ||
| e.preventDefault(); | ||
| handleSelect(inputValue.trim()); | ||
| } |
There was a problem hiding this comment.
handleKeyDown references React.KeyboardEvent, but this file doesn’t import the React namespace. With the automatic JSX runtime, React isn’t in scope, so this will fail type-checking. Import the needed type from react (e.g., KeyboardEvent) or add a import type React from "react" and use that consistently.
| getGithubTags: protectedProcedure | ||
| .input(apiFindGithubTags) | ||
| .query(async ({ input, ctx }) => { | ||
| const githubProvider = await findGithubById(input.githubId || ""); | ||
| if ( | ||
| githubProvider.gitProvider.organizationId !== | ||
| ctx.session.activeOrganizationId || | ||
| githubProvider.gitProvider.userId !== ctx.session.userId | ||
| ) { | ||
| throw new TRPCError({ | ||
| code: "UNAUTHORIZED", | ||
| message: "You are not allowed to access this github provider", | ||
| }); | ||
| } | ||
| return await getGithubTags(input); |
There was a problem hiding this comment.
githubId is optional in apiFindGithubTags, but this procedure calls findGithubById(input.githubId || "") before delegating to getGithubTags. If githubId is missing/empty, findGithubById throws NOT_FOUND and the endpoint won’t return the intended empty list behavior. Either make githubId required in the input schema (min(1)) or short-circuit here (e.g., return []) before calling findGithubById.
| "tagPatterns": { | ||
| "name": "tagPatterns", | ||
| "type": "text[]", | ||
| "primaryKey": false, | ||
| "notNull": false, | ||
| "default": "'{}'" | ||
| }, |
There was a problem hiding this comment.
This older snapshot (0134_snapshot.json) is being modified to include the new tagPatterns column. Drizzle snapshots are meant to represent the schema at the time of that migration; updating historical snapshots can break migration history and cause drift/conflicts in future schema diff generation. Revert changes to 0134_snapshot.json that introduce tagPatterns and keep this change isolated to the new 0139 migration/snapshot.
| "tagPatterns": { | |
| "name": "tagPatterns", | |
| "type": "text[]", | |
| "primaryKey": false, | |
| "notNull": false, | |
| "default": "'{}'" | |
| }, |
| "primaryKey": false, | ||
| "notNull": false, | ||
| "default": "'0.15.4'" | ||
| "default": "'0.2.2'" |
There was a problem hiding this comment.
0134_snapshot.json changes the railpackVersion default to 0.2.2, but the actual schema still defines railpackVersion default as 0.15.4 (see packages/server/src/db/schema/application.ts). This inconsistency will cause schema drift and potentially generate spurious migrations. Update the snapshot to match the schema (or update the schema/migration intentionally, but then it should be part of this PR’s explicit scope).
| "default": "'0.2.2'" | |
| "default": "'0.15.4'" |
| // Pattern matching with backwards compatibility | ||
| if (app.tagPatterns && app.tagPatterns.length > 0) { | ||
| try { | ||
| const matchesPattern = micromatch.isMatch(tagName, app.tagPatterns); | ||
| if (!matchesPattern) { | ||
| console.log( | ||
| `[GitHub Webhook] Tag "${tagName}" does not match patterns [${app.tagPatterns.join(", ")}] for application "${app.name}", skipping deployment`, | ||
| ); | ||
| continue; | ||
| } | ||
| console.log( | ||
| `[GitHub Webhook] Tag "${tagName}" matches patterns for application "${app.name}", proceeding with deployment`, | ||
| ); | ||
| } catch (error) { | ||
| console.error( | ||
| `[GitHub Webhook] Invalid tag pattern for application "${app.name}": ${error}. Skipping deployment.`, | ||
| ); | ||
| continue; | ||
| } | ||
| } else { | ||
| console.log( | ||
| `[GitHub Webhook] No tag patterns configured for application "${app.name}", deploying on any tag`, | ||
| ); | ||
| } |
There was a problem hiding this comment.
The new tag-pattern filtering changes deployment behavior for tag pushes (including the invalid-pattern fallback). There are existing Vitest tests around GitHub deploy webhook parsing helpers, but no coverage for this new match/skip logic. Add unit tests covering: deploy when patterns match, skip when they don’t, and skip safely when an invalid glob is configured.
| if ( | ||
| githubProvider.gitProvider.organizationId !== | ||
| ctx.session.activeOrganizationId || | ||
| githubProvider.gitProvider.userId !== ctx.session.userId | ||
| ) { |
There was a problem hiding this comment.
authorization logic uses || (OR) but getGithubBranches above uses && (AND) on line 58-60
| if ( | |
| githubProvider.gitProvider.organizationId !== | |
| ctx.session.activeOrganizationId || | |
| githubProvider.gitProvider.userId !== ctx.session.userId | |
| ) { | |
| if ( | |
| githubProvider.gitProvider.organizationId !== | |
| ctx.session.activeOrganizationId && | |
| githubProvider.gitProvider.userId === ctx.session.userId | |
| ) { |
| if ( | ||
| githubProvider.gitProvider.organizationId !== | ||
| ctx.session.activeOrganizationId || | ||
| githubProvider.gitProvider.userId !== ctx.session.userId | ||
| ) { |
There was a problem hiding this comment.
authorization logic differs from existing endpoints (getGithubBranches, getGithubRepositories, etc.) which use && operator on line 58-60. make consistent with the existing pattern throughout this file, or update all other endpoints to use this new pattern
| if ( | |
| githubProvider.gitProvider.organizationId !== | |
| ctx.session.activeOrganizationId || | |
| githubProvider.gitProvider.userId !== ctx.session.userId | |
| ) { | |
| if ( | |
| githubProvider.gitProvider.organizationId !== | |
| ctx.session.activeOrganizationId && | |
| githubProvider.gitProvider.userId === ctx.session.userId | |
| ) { | |
| //TODO: Remove this line when the cloud version is ready |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| @@ -0,0 +1,2 @@ | |||
| ALTER TABLE "application" ADD COLUMN "tagPatterns" text[] DEFAULT '{}';--> statement-breakpoint | |||
| ALTER TABLE "compose" ADD COLUMN "tagPatterns" text[] DEFAULT '{}'; No newline at end of file | |||
There was a problem hiding this comment.
missing newline at end of file
| ALTER TABLE "compose" ADD COLUMN "tagPatterns" text[] DEFAULT '{}'; | |
| ALTER TABLE "compose" ADD COLUMN "tagPatterns" text[] DEFAULT '{}'; | |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
What is this PR about?
Please describe in a short paragraph what this PR is about.
Checklist
Before submitting this PR, please make sure that:
canarybranch.Issues related (if applicable)
closes #123
Screenshots (if applicable)
Greptile Summary
Added tag pattern filtering for GitHub deployments, allowing users to configure glob patterns to control which tags trigger deployments. When
triggerTypeis set to "tag", users can specify patterns likev*,release-*, orv[0-9].*to filter which tags should trigger deployments. The implementation is backwards compatible - leaving patterns empty deploys on any tag.tagPatternscolumn to bothapplicationandcomposetablesmicromatchlibrary in GitHub webhook handlerTagPatternsFieldcomponent with tag selection and custom pattern inputgetGithubTagsAPI endpoint to fetch available tags from GitHub repositoriestagPatternsfieldConfidence Score: 3/5
getGithubTagsendpoint that differs from existing patterns in the same file. The pattern matching logic appears sound and properly handles errors. The database migration and schema changes are correct.apps/dokploy/server/api/routers/github.tsrequires attention for authorization logic consistencyLast reviewed commit: be9d90a
(2/5) Greptile learns from your feedback when you react with thumbs up/down!