Skip to content

Commit d490111

Browse files
authored
Merge pull request #3441 from Dokploy/3260-dokploy-automatically-updates-itself-but-automated-updates-are-disabled-in-the-settings
chore(dependencies): update semver to version 7.7.3 and add @types/se…
2 parents ccfd7f5 + 167dacc commit d490111

File tree

5 files changed

+124
-61
lines changed

5 files changed

+124
-61
lines changed

apps/dokploy/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,11 @@
153153
"xterm-addon-fit": "^0.8.0",
154154
"yaml": "2.8.1",
155155
"zod": "^3.25.32",
156-
"zod-form-data": "^2.0.7"
156+
"zod-form-data": "^2.0.7",
157+
"semver": "7.7.3"
157158
},
158159
"devDependencies": {
160+
"@types/semver": "7.7.1",
159161
"@types/shell-quote": "^1.7.5",
160162
"@types/adm-zip": "^0.5.7",
161163
"@types/bcrypt": "5.0.2",

apps/dokploy/server/api/routers/settings.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export const settingsRouter = createTRPCRouter({
8888
if (IS_CLOUD) {
8989
return true;
9090
}
91-
await reloadDockerResource("dokploy");
91+
await reloadDockerResource("dokploy", undefined, packageInfo.version);
9292
return true;
9393
}),
9494
cleanRedis: adminProcedure.mutation(async () => {
@@ -399,7 +399,7 @@ export const settingsRouter = createTRPCRouter({
399399
return DEFAULT_UPDATE_DATA;
400400
}
401401

402-
return await getUpdateData();
402+
return await getUpdateData(packageInfo.version);
403403
}),
404404
updateServer: adminProcedure.mutation(async () => {
405405
if (IS_CLOUD) {

packages/server/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,11 @@
7878
"ssh2": "1.15.0",
7979
"toml": "3.0.0",
8080
"ws": "8.16.0",
81-
"zod": "^3.25.32"
81+
"zod": "^3.25.32",
82+
"semver": "7.7.3"
8283
},
8384
"devDependencies": {
85+
"@types/semver": "7.7.1",
8486
"@types/adm-zip": "^0.5.7",
8587
"@types/bcrypt": "5.0.2",
8688
"@types/dockerode": "3.3.23",

packages/server/src/services/settings.ts

Lines changed: 89 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import {
55
execAsync,
66
execAsyncRemote,
77
} from "@dokploy/server/utils/process/execAsync";
8+
import semver from "semver";
89
import {
910
initializeStandaloneTraefik,
1011
initializeTraefikService,
1112
type TraefikOptions,
1213
} from "../setup/traefik-setup";
13-
1414
export interface IUpdateData {
1515
latestVersion: string | null;
1616
updateAvailable: boolean;
@@ -55,56 +55,95 @@ export const getServiceImageDigest = async () => {
5555
};
5656

5757
/** Returns latest version number and information whether server update is available by comparing current image's digest against digest for provided image tag via Docker hub API. */
58-
export const getUpdateData = async (): Promise<IUpdateData> => {
59-
let currentDigest: string;
58+
export const getUpdateData = async (
59+
currentVersion: string,
60+
): Promise<IUpdateData> => {
6061
try {
61-
currentDigest = await getServiceImageDigest();
62-
} catch (error) {
63-
// TODO: Docker versions 29.0.0 change the way to get the service image digest, so we need to update this in the future we upgrade to that version.
64-
return DEFAULT_UPDATE_DATA;
65-
}
62+
const baseUrl =
63+
"https://hub.docker.com/v2/repositories/dokploy/dokploy/tags";
64+
let url: string | null = `${baseUrl}?page_size=100`;
65+
let allResults: { digest: string; name: string }[] = [];
66+
67+
// Fetch all tags from Docker Hub
68+
while (url) {
69+
const response = await fetch(url, {
70+
method: "GET",
71+
headers: { "Content-Type": "application/json" },
72+
});
73+
74+
const data = (await response.json()) as {
75+
next: string | null;
76+
results: { digest: string; name: string }[];
77+
};
78+
79+
allResults = allResults.concat(data.results);
80+
url = data?.next;
81+
}
6682

67-
const baseUrl = "https://hub.docker.com/v2/repositories/dokploy/dokploy/tags";
68-
let url: string | null = `${baseUrl}?page_size=100`;
69-
let allResults: { digest: string; name: string }[] = [];
70-
while (url) {
71-
const response = await fetch(url, {
72-
method: "GET",
73-
headers: { "Content-Type": "application/json" },
74-
});
83+
const currentImageTag = getDokployImageTag();
84+
85+
// Special handling for canary and feature branches
86+
// For development versions (canary/feature), don't perform update checks
87+
// These are unstable versions that change frequently, and users on these
88+
// branches are expected to manually manage updates
89+
if (currentImageTag === "canary" || currentImageTag === "feature") {
90+
const currentDigest = await getServiceImageDigest();
91+
const latestDigest = allResults.find(
92+
(t) => t.name === currentImageTag,
93+
)?.digest;
94+
if (!latestDigest) {
95+
return DEFAULT_UPDATE_DATA;
96+
}
97+
if (currentDigest !== latestDigest) {
98+
return {
99+
latestVersion: currentImageTag,
100+
updateAvailable: true,
101+
};
102+
}
103+
return {
104+
latestVersion: currentImageTag,
105+
updateAvailable: false,
106+
};
107+
}
75108

76-
const data = (await response.json()) as {
77-
next: string | null;
78-
results: { digest: string; name: string }[];
79-
};
109+
// For stable versions, use semver comparison
110+
// Find the "latest" tag and get its digest
111+
const latestTag = allResults.find((t) => t.name === "latest");
80112

81-
allResults = allResults.concat(data.results);
82-
url = data?.next;
83-
}
113+
if (!latestTag) {
114+
return DEFAULT_UPDATE_DATA;
115+
}
84116

85-
const imageTag = getDokployImageTag();
86-
const searchedDigest = allResults.find((t) => t.name === imageTag)?.digest;
117+
// Find the versioned tag (v0.x.x) that has the same digest as "latest"
118+
const latestVersionTag = allResults.find(
119+
(t) => t.digest === latestTag.digest && t.name.startsWith("v"),
120+
);
87121

88-
if (!searchedDigest) {
89-
return DEFAULT_UPDATE_DATA;
90-
}
122+
if (!latestVersionTag) {
123+
return DEFAULT_UPDATE_DATA;
124+
}
91125

92-
if (imageTag === "latest") {
93-
const versionedTag = allResults.find(
94-
(t) => t.digest === searchedDigest && t.name.startsWith("v"),
95-
);
126+
const latestVersion = latestVersionTag.name;
127+
128+
// Use semver to compare versions for stable releases
129+
const cleanedCurrent = semver.clean(currentVersion);
130+
const cleanedLatest = semver.clean(latestVersion);
96131

97-
if (!versionedTag) {
132+
if (!cleanedCurrent || !cleanedLatest) {
98133
return DEFAULT_UPDATE_DATA;
99134
}
100135

101-
const { name: latestVersion, digest } = versionedTag;
102-
const updateAvailable = digest !== currentDigest;
136+
// Check if the latest version is greater than the current version
137+
const updateAvailable = semver.gt(cleanedLatest, cleanedCurrent);
103138

104-
return { latestVersion, updateAvailable };
139+
return {
140+
latestVersion,
141+
updateAvailable,
142+
};
143+
} catch (error) {
144+
console.error("Error fetching update data:", error);
145+
return DEFAULT_UPDATE_DATA;
105146
}
106-
const updateAvailable = searchedDigest !== currentDigest;
107-
return { latestVersion: imageTag, updateAvailable };
108147
};
109148

110149
interface TreeDataItem {
@@ -254,11 +293,22 @@ fi`;
254293
export const reloadDockerResource = async (
255294
resourceName: string,
256295
serverId?: string,
296+
version?: string,
257297
) => {
258298
const resourceType = await getDockerResourceType(resourceName, serverId);
259299
let command = "";
260300
if (resourceType === "service") {
261-
command = `docker service update --force ${resourceName}`;
301+
if (resourceName === "dokploy") {
302+
const currentImageTag = getDokployImageTag();
303+
let imageTag = version;
304+
if (currentImageTag === "canary" || currentImageTag === "feature") {
305+
imageTag = currentImageTag;
306+
}
307+
308+
command = `docker service update --force --image dokploy/dokploy:${imageTag} ${resourceName}`;
309+
} else {
310+
command = `docker service update --force ${resourceName}`;
311+
}
262312
} else if (resourceType === "standalone") {
263313
command = `docker restart ${resourceName}`;
264314
} else {

pnpm-lock.yaml

Lines changed: 27 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)