@@ -5,12 +5,12 @@ import {
55 execAsync ,
66 execAsyncRemote ,
77} from "@dokploy/server/utils/process/execAsync" ;
8+ import semver from "semver" ;
89import {
910 initializeStandaloneTraefik ,
1011 initializeTraefikService ,
1112 type TraefikOptions ,
1213} from "../setup/traefik-setup" ;
13-
1414export 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
110149interface TreeDataItem {
@@ -254,11 +293,22 @@ fi`;
254293export 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 {
0 commit comments