Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
824789b
feat: add template variable interpolation for preview deployment subd…
ajnart May 4, 2026
26440e8
feat: add preview domain template UI with live preview and tests
ajnart May 4, 2026
709eb0c
[autofix.ci] apply automated fixes
autofix-ci[bot] May 4, 2026
9f10f0f
fix(migrate-auth-secret): exit cleanly when there are no 2FA records
ngenohkevin May 12, 2026
a714e0f
Merge pull request #4394 from ngenohkevin/fix/migrate-auth-secret-exi…
Siumauricio May 12, 2026
754774e
feat(compose): add import from base64 in create service dropdown
Siumauricio May 12, 2026
63e33a2
[autofix.ci] apply automated fixes
autofix-ci[bot] May 12, 2026
7a568aa
Merge pull request #4395 from Dokploy/feat/import-compose-from-base64
Siumauricio May 12, 2026
f8fcf68
Enhance version synchronization workflow to include SDK repository
Siumauricio May 12, 2026
558d809
feat(deployment): add readLogs procedure to fetch deployment logs
Siumauricio May 13, 2026
aff200f
feat(deployment): add server access validation for deployment actions
Siumauricio May 13, 2026
67278d8
feat(organization): prevent inviting users with owner role
Siumauricio May 13, 2026
1fdbe87
feat(user): implement session cleanup on user update
Siumauricio May 13, 2026
a50f958
feat(settings): add copy button to server IP in web server settings (…
Siumauricio May 13, 2026
8d88a34
fix: copy Dokploy server IP when clicking server badge (#4390)
vadamk May 13, 2026
ef0cf9b
fix: responsive layout (#4391)
nhridoy May 13, 2026
6e342ee
fix: automatically converting username to lowercase both in creation …
Baker May 13, 2026
59fea5f
Merge remote-tracking branch 'upstream/canary' into feat/custom-previ…
ajnart May 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/workflows/dokploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ jobs:
needs: [combine-manifests]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -160,3 +162,80 @@ jobs:
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

sync-version:
needs: [generate-release]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Sync version to MCP repository
run: |
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/mcp.git /tmp/mcp-repo
cd /tmp/mcp-repo

jq --arg v "${{ needs.generate-release.outputs.version }}" '.version = $v' package.json > package.json.tmp
mv package.json.tmp package.json

npm install -g pnpm
pnpm install
pnpm run fetch-openapi
pnpm run generate

git config user.name "Dokploy Bot"
git config user.email "bot@dokploy.com"
git add -A
git commit -m "chore: bump version to ${{ needs.generate-release.outputs.version }}" \
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
--allow-empty
git push

echo "✅ MCP repo synced to version ${{ needs.generate-release.outputs.version }}"

- name: Sync version to CLI repository
run: |
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/cli.git /tmp/cli-repo
cd /tmp/cli-repo

jq --arg v "${{ needs.generate-release.outputs.version }}" '.version = $v' package.json > package.json.tmp
mv package.json.tmp package.json

cp ${{ github.workspace }}/openapi.json ./openapi.json
npm install -g pnpm
pnpm install
pnpm run generate

git config user.name "Dokploy Bot"
git config user.email "bot@dokploy.com"
git add -A
git commit -m "chore: bump version to ${{ needs.generate-release.outputs.version }}" \
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
--allow-empty
git push

echo "✅ CLI repo synced to version ${{ needs.generate-release.outputs.version }}"

- name: Sync version to SDK repository
run: |
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/sdk.git /tmp/sdk-repo
cd /tmp/sdk-repo

jq --arg v "${{ needs.generate-release.outputs.version }}" '.version = $v' package.json > package.json.tmp
mv package.json.tmp package.json

cp ${{ github.workspace }}/openapi.json ./openapi.json
npm install -g pnpm
pnpm install
pnpm run generate

git config user.name "Dokploy Bot"
git config user.email "bot@dokploy.com"
git add -A
git commit -m "chore: bump version to ${{ needs.generate-release.outputs.version }}" \
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
--allow-empty
git push

echo "✅ SDK repo synced to version ${{ needs.generate-release.outputs.version }}"
83 changes: 0 additions & 83 deletions .github/workflows/sync-version.yml

This file was deleted.

80 changes: 80 additions & 0 deletions apps/dokploy/__test__/preview-deployment/template.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { interpolateSubdomainTemplate } from "../../../../packages/server/src/services/preview-deployment";
import { expect, test } from "vitest";

const baseVars = {
appName: "my-app",
prNumber: "123",
branchName: "feature/login-page",
uniqueId: "abc123",
};

test("replaces ${prNumber} variable", () => {
const result = interpolateSubdomainTemplate(
"${prNumber}.previews.example.com",
baseVars,
);
expect(result).toBe("123.previews.example.com");
});

test("replaces ${branchName} with slugified value", () => {
const result = interpolateSubdomainTemplate(
"${branchName}.previews.example.com",
baseVars,
);
expect(result).toBe("feature-login-page.previews.example.com");
});

test("replaces ${appName} variable", () => {
const result = interpolateSubdomainTemplate(
"${appName}-${prNumber}.example.com",
baseVars,
);
expect(result).toBe("my-app-123.example.com");
});

test("replaces ${uniqueId} variable", () => {
const result = interpolateSubdomainTemplate(
"preview-${uniqueId}.example.com",
baseVars,
);
expect(result).toBe("preview-abc123.example.com");
});

test("replaces all variables in a complex template", () => {
const result = interpolateSubdomainTemplate(
"${appName}-pr${prNumber}-${branchName}.example.com",
baseVars,
);
expect(result).toBe("my-app-pr123-feature-login-page.example.com");
});

test("leaves template unchanged when no variables present", () => {
const result = interpolateSubdomainTemplate("*.traefik.me", baseVars);
expect(result).toBe("*.traefik.me");
});

test("slugifies branch names with special characters", () => {
const result = interpolateSubdomainTemplate("${branchName}.example.com", {
...baseVars,
branchName: "feat/SOME_THING@v2.0",
});
expect(result).toBe("feat-some-thing-v2-0.example.com");
});

test("truncates slugified branch to 63 chars for DNS compliance", () => {
const longBranch = "a".repeat(100);
const result = interpolateSubdomainTemplate("${branchName}.example.com", {
...baseVars,
branchName: longBranch,
});
const subdomain = result.split(".")[0]!;
expect(subdomain.length).toBeLessThanOrEqual(63);
});

test("handles multiple occurrences of the same variable", () => {
const result = interpolateSubdomainTemplate(
"${prNumber}-${prNumber}.example.com",
baseVars,
);
expect(result).toBe("123-123.example.com");
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
import { HelpCircle, Plus, Settings2, X } from "lucide-react";
import { useEffect, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
Expand Down Expand Up @@ -102,7 +102,32 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {

const previewHttps = form.watch("previewHttps");
const wildcardDomain = form.watch("wildcardDomain");
const isTraefikMeDomain = wildcardDomain?.includes("sslip.io") || false;
const isSslipDomain = wildcardDomain?.includes("sslip.io") || false;

const templatePreview = useMemo(() => {
const template = wildcardDomain || "*.sslip.io";
const appName = data?.appName || "my-app";
const exampleVars: Record<string, string> = {
appName,
prNumber: "42",
branchName: "feature-login",
uniqueId: "a1b2c3",
};
let result = template.replace(
/\$\{(appName|prNumber|branchName|uniqueId)\}/g,
(_match: string, key: string) => exampleVars[key] ?? "",
);
const hasUniqueVar =
template.includes("${prNumber}") ||
template.includes("${branchName}") ||
template.includes("${uniqueId}");
if (!hasUniqueVar) {
result = result.replace("*", `${appName}-a1b2c3`);
} else {
result = result.replace("*", appName);
}
return result;
}, [wildcardDomain, data?.appName]);

useEffect(() => {
setIsEnabled(data?.isPreviewDeploymentsActive || false);
Expand Down Expand Up @@ -171,7 +196,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
{isTraefikMeDomain && (
{isSslipDomain && (
<AlertBlock type="info">
<strong>Note:</strong> sslip.io is a public HTTP service and
does not support SSL/HTTPS. HTTPS and certificate options will
Expand All @@ -189,11 +214,33 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
control={form.control}
name="wildcardDomain"
render={({ field }) => (
<FormItem>
<FormLabel>Wildcard Domain</FormLabel>
<FormItem className="lg:col-span-2">
<FormLabel>Preview Domain Template</FormLabel>
<FormControl>
<Input placeholder="*.sslip.io" {...field} />
<Input
placeholder="*.sslip.io or ${prNumber}.example.com"
{...field}
/>
</FormControl>
<FormDescription className="flex flex-col gap-1.5">
<span>
Use <code className="text-xs">*.</code> for
auto-generated subdomains, or template variables for
custom patterns. If the template includes{" "}
<code className="text-xs">{"${prNumber}"}</code> or{" "}
<code className="text-xs">{"${branchName}"}</code>,
no random suffix is appended.
</span>
<span className="flex flex-wrap gap-x-3 gap-y-1 text-xs font-mono">
<code>{"${appName}"}</code>
<code>{"${prNumber}"}</code>
<code>{"${branchName}"}</code>
<code>{"${uniqueId}"}</code>
</span>
<span className="text-xs mt-1 px-2 py-1 rounded bg-muted font-mono truncate">
Example: {templatePreview}
</span>
</FormDescription>
<FormMessage />
</FormItem>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export function AnalyzeLogs({ logs, context }: Props) {
disabled={logs.length === 0}
title="Analyze logs with AI"
>
<Bot className="mr-2 h-4 w-4" />
<Bot className="mr-2 size-4" />
AI
</Button>
</PopoverTrigger>
Expand Down
Loading
Loading