Dosu LogoDosu Logo
Ask
Join our Discord
Dokploy's SpacePublic
Dokploy
DocumentsDokploy's Space
Preview Deployment Access Control
Preview Deployment Access Control
Type
Topic
Status
Published
Created
Jul 24, 2026
Updated
Jul 24, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

Preview Deployment Access Control#

Preview deployments in Dokploy are triggered by pull_request webhook events from GitHub. Because preview builds execute arbitrary code and run with access to server environment variables, the webhook handler at /api/deploy/github enforces three stacked access-control mechanisms before creating a deployment: PR author validation, collaborator permission gating, and label-based filtering.

All three checks happen inside the pull_request event branch, applied per-application after the HMAC-SHA-256 webhook signature is verified .


Collaborator Permission Gate#

Default behavior: enabled. When app.previewRequireCollaboratorPermissions !== false, the handler calls checkUserRepositoryPermissions for the PR author before allowing a deployment .

checkUserRepositoryPermissions calls octokit.rest.repos.getCollaboratorPermissionLevel and allows only three of GitHub's five permission levels: write, maintain, or admin . Users with read or triage — including all external fork contributors — are denied.

Fail-safe design: if GitHub returns a 404 (user is not a collaborator at all), the function returns { hasWriteAccess: false, permission: null } rather than throwing, so permission errors default to deny . Errors during permission checks also block the app .

This feature was introduced by PR #2192 and added the previewRequireCollaboratorPermissions boolean column to the application table (default true). The setting can be toggled off in the application's preview settings UI — but disabling it on public repositories is explicitly discouraged .


Security Notification Comment#

When any app blocks a PR author, the handler calls createSecurityBlockedComment to post an explanatory comment to the PR . The comment identifies the blocked user, their actual permission level, the required level, and instructions for resolution.

To avoid duplicate comments on subsequent pushes to the same PR, hasExistingSecurityComment scans the PR's existing comments for the marker string "🚨 Preview Deployment Blocked - Security Protection" before posting .


Label-Based Filtering#

If an application sets previewLabels (a text[] column added by PR #2231), only PRs carrying at least one matching label will trigger a preview build. PRs without a matching label are silently skipped for that app .

When previewLabels is empty, the label check is bypassed entirely — all PRs qualify .

The labeled PR action is treated as a deployment trigger (shouldCreateDeployment = true), which means adding a qualifying label to an already-open PR creates a new preview deployment if one doesn't exist yet . This was a deliberate fix in PR #3960 to handle the case where a PR was opened before its required label was added. The unlabeled action is recognized but does not trigger a new deployment .


Preview Limit#

After label filtering, the handler enforces a per-app previewLimit. If the current number of preview deployments for an app equals or exceeds the limit, the PR is skipped . A previewLimit of 0 means no limit.


Decision Flow#

pull_request event (opened / synchronize / reopened / labeled / unlabeled)
│
├── Missing prAuthor? → 400 error
│
├── For each app with isPreviewDeploymentsActive = true:
│ ├── previewRequireCollaboratorPermissions !== false?
│ │ ├── checkUserRepositoryPermissions()
│ │ │ ├── write / maintain / admin → proceed
│ │ │ └── read / triage / none → block + security comment (deduplicated)
│ │
│ ├── previewLabels configured?
│ │ ├── PR has matching label → proceed
│ │ └── No match → skip
│ │
│ ├── previewDeployments.length > previewLimit? → skip
│ │
│ └── shouldCreateDeployment (opened/sync/reopened/labeled)?
│ ├── No existing preview → createPreviewDeployment()
│ └── Existing preview → re-queue deploy job
│
└── unlabeled → no new deployment created

Key Files#

FilePurpose
apps/dokploy/pages/api/deploy/github.tsWebhook handler — all three access-control checks
packages/server/src/utils/providers/github.tscheckUserRepositoryPermissions — GitHub API permission check
packages/server/src/services/github.tscreateSecurityBlockedComment, hasExistingSecurityComment, getSecurityBlockedMessage
packages/server/src/db/schema/application.tspreviewRequireCollaboratorPermissions (bool, default true), previewLabels (text[]), previewLimit (int) columns
apps/dokploy/components/dashboard/application/preview-deployments/show-preview-settings.tsxUI toggle for previewRequireCollaboratorPermissions and previewLabels
Documents
API Key Management
Authentication Secret Management
Backup Command Execution
Backup File Management
Build Type Schema and UI Validation
Client IP Preservation
Container Log Streaming
Database Management
Deployment Lifecycle Management
Deployment List Rendering
Docker Compose Build Caching
Docker Compose File Management
Docker Compose Networking
Docker Container Terminal
Docker Remote Build Execution
Docker Swarm Deployment
Docker Swarm Networking
Dokploy Access Control
Dokploy Core Infrastructure
Dokploy Installation & Uninstallation
Dokploy Monitoring
Git Provider Configuration
Preview Deployment Access Control
Preview Deployment Management
Railpack Build Integration
Redirect Configuration
Remote Command Execution
Remote Server Setup
Schedule and Service Permission Model
SCIM and SSO Integration
Server Settings Management
Session Management
Static Site Serving
Tailwind CSS & Shadcn UI
Terminal Styling
Timezone-Aware Scheduling
Traefik Access Log Parsing
Traefik Configuration
Traefik Docker Network Routing
Traefik Domain Routing
Traefik TLS & Certificate Management
Webhook Endpoint Architecture
AGENTS
CONTRIBUTING
Dokploy API Changes (December 2025 - February 2026)
README
README
TERMS_AND_CONDITIONS
copilot-instructions
pull_request_template