fix: strip credentials from service-level API responses (#4564)

* fix: strip credentials from service-level API responses

Registry passwords and S3 destination credentials were being returned
in service `.one` tRPC endpoints to any user with service-level read
access. Reported by Nihon Kohden Corporation security team.

- Strip registry `password` from `findApplicationById` via Drizzle `columns: { password: false }`
- Strip destination `accessKey`/`secretAccessKey` from all DB service finders (postgres, mysql, mariadb, mongo, libsql, compose, backup, volume-backups)
- Add `findRegistryByIdWithCredentials` for internal use only
- Builders and upload utils now load registry credentials by ID at execution time
- `createRollback` enriches `fullContext` with registry credentials before persisting to DB so rollback execution has what it needs
- Remove `findApplicationByIdWithCredentials` and `ApplicationNestedWithCredentials` — no longer needed
- Backup execution utils load full destination via `findDestinationById` at runtime instead of reading from the joined relation

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Mauricio Siu
2026-06-06 17:45:24 -06:00
committed by GitHub
parent f35f3064e9
commit c968a2755e
22 changed files with 169 additions and 82 deletions
+4 -8
View File
@@ -95,26 +95,22 @@ export const findApplicationById = async (applicationId: string) => {
const application = await db.query.applications.findFirst({ const application = await db.query.applications.findFirst({
where: eq(applications.applicationId, applicationId), where: eq(applications.applicationId, applicationId),
with: { with: {
environment: { environment: { with: { project: true } },
with: {
project: true,
},
},
domains: true, domains: true,
deployments: true, deployments: true,
mounts: true, mounts: true,
redirects: true, redirects: true,
security: true, security: true,
ports: true, ports: true,
registry: true,
gitlab: true, gitlab: true,
github: true, github: true,
bitbucket: true, bitbucket: true,
gitea: true, gitea: true,
server: true, server: true,
previewDeployments: true, previewDeployments: true,
buildRegistry: true, registry: { columns: { password: false } },
rollbackRegistry: true, buildRegistry: { columns: { password: false } },
rollbackRegistry: { columns: { password: false } },
}, },
}); });
if (!application) { if (!application) {
+12 -2
View File
@@ -34,7 +34,12 @@ export const findBackupById = async (backupId: string) => {
mariadb: true, mariadb: true,
mongo: true, mongo: true,
libsql: true, libsql: true,
destination: true, destination: {
columns: {
accessKey: false,
secretAccessKey: false,
},
},
compose: true, compose: true,
}, },
}); });
@@ -83,7 +88,12 @@ export const findBackupsByDbId = async (
mariadb: true, mariadb: true,
mongo: true, mongo: true,
libsql: true, libsql: true,
destination: true, destination: {
columns: {
accessKey: false,
secretAccessKey: false,
},
},
}, },
}); });
return result || []; return result || [];
+6 -1
View File
@@ -131,7 +131,12 @@ export const findComposeById = async (composeId: string) => {
server: true, server: true,
backups: { backups: {
with: { with: {
destination: true, destination: {
columns: {
accessKey: false,
secretAccessKey: false,
},
},
deployments: true, deployments: true,
}, },
}, },
+6 -1
View File
@@ -63,7 +63,12 @@ export const findLibsqlById = async (libsqlId: string) => {
server: true, server: true,
backups: { backups: {
with: { with: {
destination: true, destination: {
columns: {
accessKey: false,
secretAccessKey: false,
},
},
deployments: true, deployments: true,
}, },
}, },
+6 -1
View File
@@ -68,7 +68,12 @@ export const findMariadbById = async (mariadbId: string) => {
server: true, server: true,
backups: { backups: {
with: { with: {
destination: true, destination: {
columns: {
accessKey: false,
secretAccessKey: false,
},
},
deployments: true, deployments: true,
}, },
}, },
+6 -1
View File
@@ -63,7 +63,12 @@ export const findMongoById = async (mongoId: string) => {
server: true, server: true,
backups: { backups: {
with: { with: {
destination: true, destination: {
columns: {
accessKey: false,
secretAccessKey: false,
},
},
deployments: true, deployments: true,
}, },
}, },
+6 -1
View File
@@ -66,7 +66,12 @@ export const findMySqlById = async (mysqlId: string) => {
server: true, server: true,
backups: { backups: {
with: { with: {
destination: true, destination: {
columns: {
accessKey: false,
secretAccessKey: false,
},
},
deployments: true, deployments: true,
}, },
}, },
+6 -1
View File
@@ -76,7 +76,12 @@ export const findPostgresById = async (postgresId: string) => {
server: true, server: true,
backups: { backups: {
with: { with: {
destination: true, destination: {
columns: {
accessKey: false,
secretAccessKey: false,
},
},
deployments: true, deployments: true,
}, },
}, },
+13
View File
@@ -162,6 +162,19 @@ export const findRegistryById = async (registryId: string) => {
return registryResponse; return registryResponse;
}; };
export const findRegistryByIdWithCredentials = async (registryId: string) => {
const registryResponse = await db.query.registry.findFirst({
where: eq(registry.registryId, registryId),
});
if (!registryResponse) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Registry not found",
});
}
return registryResponse;
};
export const findAllRegistryByOrganizationId = async ( export const findAllRegistryByOrganizationId = async (
organizationId: string, organizationId: string,
) => { ) => {
+37 -16
View File
@@ -7,7 +7,6 @@ import {
deployments as deploymentsSchema, deployments as deploymentsSchema,
rollbacks, rollbacks,
} from "../db/schema"; } from "../db/schema";
import type { ApplicationNested } from "../utils/builders";
import { getRegistryTag } from "../utils/cluster/upload"; import { getRegistryTag } from "../utils/cluster/upload";
import { import {
calculateResources, calculateResources,
@@ -23,7 +22,11 @@ import { findDeploymentById } from "./deployment";
import type { Mount } from "./mount"; import type { Mount } from "./mount";
import type { Port } from "./port"; import type { Port } from "./port";
import type { Project } from "./project"; import type { Project } from "./project";
import { type Registry, safeDockerLoginCommand } from "./registry"; import {
findRegistryByIdWithCredentials,
type Registry,
safeDockerLoginCommand,
} from "./registry";
export const createRollback = async ( export const createRollback = async (
input: z.infer<typeof createRollbackSchema>, input: z.infer<typeof createRollbackSchema>,
@@ -56,11 +59,29 @@ export const createRollback = async (
...rest ...rest
} = await findApplicationById(deployment.applicationId); } = await findApplicationById(deployment.applicationId);
const registry = rest.registryId
? await findRegistryByIdWithCredentials(rest.registryId)
: rest.registry;
const buildRegistry = rest.buildRegistryId
? await findRegistryByIdWithCredentials(rest.buildRegistryId)
: rest.buildRegistry;
const rollbackRegistry = rest.rollbackRegistryId
? await findRegistryByIdWithCredentials(rest.rollbackRegistryId)
: rest.rollbackRegistry;
const fullContextWithCredentials = {
...rest,
registry,
buildRegistry,
rollbackRegistry,
};
await tx await tx
.update(rollbacks) .update(rollbacks)
.set({ .set({
image: tagImage, image: tagImage,
fullContext: rest, // eslint-disable-next-line @typescript-eslint/no-explicit-any
fullContext: fullContextWithCredentials as any,
}) })
.where(eq(rollbacks.rollbackId, rollback.rollbackId)); .where(eq(rollbacks.rollbackId, rollback.rollbackId));
@@ -162,7 +183,6 @@ export const rollback = async (rollbackId: string) => {
if (!result.fullContext) { if (!result.fullContext) {
throw new Error("Rollback context not found"); throw new Error("Rollback context not found");
} }
// Use the full context for rollback
await rollbackApplication( await rollbackApplication(
application.appName, application.appName,
result.image || "", result.image || "",
@@ -198,24 +218,25 @@ const rollbackApplication = async (
}; };
mounts: Mount[]; mounts: Mount[];
ports: Port[]; ports: Port[];
rollbackRegistry?: Registry; rollbackRegistry?: Registry | null;
}, },
) => { ) => {
if (!fullContext) { if (!fullContext) {
throw new Error("Full context is required for rollback"); throw new Error("Full context is required for rollback");
} }
const rollbackRegistry = fullContext.rollbackRegistry ?? undefined;
// Ensure Docker daemon is authenticated with the rollback registry // Ensure Docker daemon is authenticated with the rollback registry
// before updating the swarm service. The authconfig in CreateServiceOptions // before updating the swarm service. The authconfig in CreateServiceOptions
// alone is not sufficient — Docker Swarm also relies on the daemon's // alone is not sufficient — Docker Swarm also relies on the daemon's
// cached credentials (~/.docker/config.json) to distribute auth to nodes. // cached credentials (~/.docker/config.json) to distribute auth to nodes.
if (fullContext.rollbackRegistry) { if (rollbackRegistry) {
await dockerLoginForRegistry(fullContext.rollbackRegistry, serverId); await dockerLoginForRegistry(rollbackRegistry, serverId);
} }
const docker = await getRemoteDocker(serverId); const docker = await getRemoteDocker(serverId);
// Use the same configuration as mechanizeDockerContainer
const { const {
env, env,
mounts, mounts,
@@ -246,7 +267,9 @@ const rollbackApplication = async (
UpdateConfig, UpdateConfig,
Networks, Networks,
Ulimits, Ulimits,
} = generateConfigContainer(fullContext as ApplicationNested); } = generateConfigContainer(
fullContext as Parameters<typeof generateConfigContainer>[0],
);
const bindsMount = generateBindMounts(mounts); const bindsMount = generateBindMounts(mounts);
const envVariables = prepareEnvironmentVariables( const envVariables = prepareEnvironmentVariables(
@@ -254,18 +277,16 @@ const rollbackApplication = async (
fullContext.environment.project.env, fullContext.environment.project.env,
); );
// Build the full registry image path if rollbackRegistry is available
// e.g., "appName:v5" -> "siumauricio/appName:v5" or "registry.com/prefix/appName:v5"
let rollbackImage = image; let rollbackImage = image;
if (fullContext.rollbackRegistry) { if (rollbackRegistry) {
rollbackImage = getRegistryTag(fullContext.rollbackRegistry, image); rollbackImage = getRegistryTag(rollbackRegistry, image);
} }
const settings: CreateServiceOptions = { const settings: CreateServiceOptions = {
authconfig: { authconfig: {
password: fullContext.rollbackRegistry?.password || "", password: rollbackRegistry?.password || "",
username: fullContext.rollbackRegistry?.username || "", username: rollbackRegistry?.username || "",
serveraddress: fullContext.rollbackRegistry?.registryUrl || "", serveraddress: rollbackRegistry?.registryUrl || "",
}, },
Name: appName, Name: appName,
TaskTemplate: { TaskTemplate: {
@@ -84,7 +84,12 @@ export const findVolumeBackupById = async (volumeBackupId: string) => {
}, },
}, },
}, },
destination: true, destination: {
columns: {
accessKey: false,
secretAccessKey: false,
},
},
}, },
}); });
+2 -1
View File
@@ -4,6 +4,7 @@ import {
createDeploymentBackup, createDeploymentBackup,
updateDeploymentStatus, updateDeploymentStatus,
} from "@dokploy/server/services/deployment"; } from "@dokploy/server/services/deployment";
import { findDestinationById } from "@dokploy/server/services/destination";
import { findEnvironmentById } from "@dokploy/server/services/environment"; import { findEnvironmentById } from "@dokploy/server/services/environment";
import { findProjectById } from "@dokploy/server/services/project"; import { findProjectById } from "@dokploy/server/services/project";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup"; import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
@@ -23,7 +24,7 @@ export const runComposeBackup = async (
const environment = await findEnvironmentById(environmentId); const environment = await findEnvironmentById(environmentId);
const project = await findProjectById(environment.projectId); const project = await findProjectById(environment.projectId);
const { prefix, databaseType, serviceName } = backup; const { prefix, databaseType, serviceName } = backup;
const destination = backup.destination; const destination = await findDestinationById(backup.destinationId);
const backupFileName = `${getBackupTimestamp()}.${databaseType === "mongo" ? "bson" : "sql"}.gz`; const backupFileName = `${getBackupTimestamp()}.${databaseType === "mongo" ? "bson" : "sql"}.gz`;
const s3AppName = serviceName ? `${appName}_${serviceName}` : appName; const s3AppName = serviceName ? `${appName}_${serviceName}` : appName;
const bucketDestination = `${s3AppName}/${normalizeS3Path(prefix)}${backupFileName}`; const bucketDestination = `${s3AppName}/${normalizeS3Path(prefix)}${backupFileName}`;
+4 -2
View File
@@ -1,6 +1,7 @@
import { CLEANUP_CRON_JOB } from "@dokploy/server/constants"; import { CLEANUP_CRON_JOB } from "@dokploy/server/constants";
import { member } from "@dokploy/server/db/schema"; import { member } from "@dokploy/server/db/schema";
import type { BackupSchedule } from "@dokploy/server/services/backup"; import type { BackupSchedule } from "@dokploy/server/services/backup";
import { findDestinationById } from "@dokploy/server/services/destination";
import { getAllServers } from "@dokploy/server/services/server"; import { getAllServers } from "@dokploy/server/services/server";
import { getWebServerSettings } from "@dokploy/server/services/web-server-settings"; import { getWebServerSettings } from "@dokploy/server/services/web-server-settings";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
@@ -131,9 +132,10 @@ export const keepLatestNBackups = async (
if (!backup.keepLatestCount) return; if (!backup.keepLatestCount) return;
try { try {
const rcloneFlags = getS3Credentials(backup.destination); const destination = await findDestinationById(backup.destinationId);
const rcloneFlags = getS3Credentials(destination);
const appName = getServiceAppName(backup); const appName = getServiceAppName(backup);
const backupFilesPath = `:s3:${backup.destination.bucket}/${appName}/${normalizeS3Path(backup.prefix)}`; const backupFilesPath = `:s3:${destination.bucket}/${appName}/${normalizeS3Path(backup.prefix)}`;
// --include "*.bson.gz" or "*.sql.gz" or "*.zip" ensures nothing else other than the dokploy backup files are touched by rclone // --include "*.bson.gz" or "*.sql.gz" or "*.zip" ensures nothing else other than the dokploy backup files are touched by rclone
const rcloneList = `rclone lsf ${rcloneFlags.join(" ")} --include "*${backup.databaseType === "web-server" ? ".zip" : ".{sql.gz,bson.gz}"}" ${backupFilesPath}`; const rcloneList = `rclone lsf ${rcloneFlags.join(" ")} --include "*${backup.databaseType === "web-server" ? ".zip" : ".{sql.gz,bson.gz}"}" ${backupFilesPath}`;
+2 -1
View File
@@ -3,6 +3,7 @@ import {
createDeploymentBackup, createDeploymentBackup,
updateDeploymentStatus, updateDeploymentStatus,
} from "@dokploy/server/services/deployment"; } from "@dokploy/server/services/deployment";
import { findDestinationById } from "@dokploy/server/services/destination";
import { findEnvironmentById } from "@dokploy/server/services/environment"; import { findEnvironmentById } from "@dokploy/server/services/environment";
import type { Libsql } from "@dokploy/server/services/libsql"; import type { Libsql } from "@dokploy/server/services/libsql";
import { findProjectById } from "@dokploy/server/services/project"; import { findProjectById } from "@dokploy/server/services/project";
@@ -29,7 +30,7 @@ export const runLibsqlBackup = async (
description: "Initializing Backup", description: "Initializing Backup",
}); });
const { prefix } = backup; const { prefix } = backup;
const destination = backup.destination; const destination = await findDestinationById(backup.destinationId);
const backupFileName = `${getBackupTimestamp()}.sql.gz`; const backupFileName = `${getBackupTimestamp()}.sql.gz`;
const bucketDestination = `${appName}/${normalizeS3Path(prefix)}${backupFileName}`; const bucketDestination = `${appName}/${normalizeS3Path(prefix)}${backupFileName}`;
try { try {
+2 -1
View File
@@ -3,6 +3,7 @@ import {
createDeploymentBackup, createDeploymentBackup,
updateDeploymentStatus, updateDeploymentStatus,
} from "@dokploy/server/services/deployment"; } from "@dokploy/server/services/deployment";
import { findDestinationById } from "@dokploy/server/services/destination";
import { findEnvironmentById } from "@dokploy/server/services/environment"; import { findEnvironmentById } from "@dokploy/server/services/environment";
import type { Mariadb } from "@dokploy/server/services/mariadb"; import type { Mariadb } from "@dokploy/server/services/mariadb";
import { findProjectById } from "@dokploy/server/services/project"; import { findProjectById } from "@dokploy/server/services/project";
@@ -23,7 +24,7 @@ export const runMariadbBackup = async (
const environment = await findEnvironmentById(environmentId); const environment = await findEnvironmentById(environmentId);
const project = await findProjectById(environment.projectId); const project = await findProjectById(environment.projectId);
const { prefix } = backup; const { prefix } = backup;
const destination = backup.destination; const destination = await findDestinationById(backup.destinationId);
const backupFileName = `${getBackupTimestamp()}.sql.gz`; const backupFileName = `${getBackupTimestamp()}.sql.gz`;
const bucketDestination = `${appName}/${normalizeS3Path(prefix)}${backupFileName}`; const bucketDestination = `${appName}/${normalizeS3Path(prefix)}${backupFileName}`;
const deployment = await createDeploymentBackup({ const deployment = await createDeploymentBackup({
+2 -1
View File
@@ -3,6 +3,7 @@ import {
createDeploymentBackup, createDeploymentBackup,
updateDeploymentStatus, updateDeploymentStatus,
} from "@dokploy/server/services/deployment"; } from "@dokploy/server/services/deployment";
import { findDestinationById } from "@dokploy/server/services/destination";
import { findEnvironmentById } from "@dokploy/server/services/environment"; import { findEnvironmentById } from "@dokploy/server/services/environment";
import type { Mongo } from "@dokploy/server/services/mongo"; import type { Mongo } from "@dokploy/server/services/mongo";
import { findProjectById } from "@dokploy/server/services/project"; import { findProjectById } from "@dokploy/server/services/project";
@@ -20,7 +21,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
const environment = await findEnvironmentById(environmentId); const environment = await findEnvironmentById(environmentId);
const project = await findProjectById(environment.projectId); const project = await findProjectById(environment.projectId);
const { prefix } = backup; const { prefix } = backup;
const destination = backup.destination; const destination = await findDestinationById(backup.destinationId);
const backupFileName = `${getBackupTimestamp()}.bson.gz`; const backupFileName = `${getBackupTimestamp()}.bson.gz`;
const bucketDestination = `${appName}/${normalizeS3Path(prefix)}${backupFileName}`; const bucketDestination = `${appName}/${normalizeS3Path(prefix)}${backupFileName}`;
const deployment = await createDeploymentBackup({ const deployment = await createDeploymentBackup({
+2 -1
View File
@@ -3,6 +3,7 @@ import {
createDeploymentBackup, createDeploymentBackup,
updateDeploymentStatus, updateDeploymentStatus,
} from "@dokploy/server/services/deployment"; } from "@dokploy/server/services/deployment";
import { findDestinationById } from "@dokploy/server/services/destination";
import { findEnvironmentById } from "@dokploy/server/services/environment"; import { findEnvironmentById } from "@dokploy/server/services/environment";
import type { MySql } from "@dokploy/server/services/mysql"; import type { MySql } from "@dokploy/server/services/mysql";
import { findProjectById } from "@dokploy/server/services/project"; import { findProjectById } from "@dokploy/server/services/project";
@@ -20,7 +21,7 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
const environment = await findEnvironmentById(environmentId); const environment = await findEnvironmentById(environmentId);
const project = await findProjectById(environment.projectId); const project = await findProjectById(environment.projectId);
const { prefix } = backup; const { prefix } = backup;
const destination = backup.destination; const destination = await findDestinationById(backup.destinationId);
const backupFileName = `${getBackupTimestamp()}.sql.gz`; const backupFileName = `${getBackupTimestamp()}.sql.gz`;
const bucketDestination = `${appName}/${normalizeS3Path(prefix)}${backupFileName}`; const bucketDestination = `${appName}/${normalizeS3Path(prefix)}${backupFileName}`;
const deployment = await createDeploymentBackup({ const deployment = await createDeploymentBackup({
@@ -3,6 +3,7 @@ import {
createDeploymentBackup, createDeploymentBackup,
updateDeploymentStatus, updateDeploymentStatus,
} from "@dokploy/server/services/deployment"; } from "@dokploy/server/services/deployment";
import { findDestinationById } from "@dokploy/server/services/destination";
import { findEnvironmentById } from "@dokploy/server/services/environment"; import { findEnvironmentById } from "@dokploy/server/services/environment";
import type { Postgres } from "@dokploy/server/services/postgres"; import type { Postgres } from "@dokploy/server/services/postgres";
import { findProjectById } from "@dokploy/server/services/project"; import { findProjectById } from "@dokploy/server/services/project";
@@ -29,7 +30,7 @@ export const runPostgresBackup = async (
description: "Initializing Backup", description: "Initializing Backup",
}); });
const { prefix } = backup; const { prefix } = backup;
const destination = backup.destination; const destination = await findDestinationById(backup.destinationId);
const backupFileName = `${getBackupTimestamp()}.sql.gz`; const backupFileName = `${getBackupTimestamp()}.sql.gz`;
const bucketDestination = `${appName}/${normalizeS3Path(prefix)}${backupFileName}`; const bucketDestination = `${appName}/${normalizeS3Path(prefix)}${backupFileName}`;
try { try {
+21 -22
View File
@@ -1,3 +1,4 @@
import { findRegistryByIdWithCredentials } from "@dokploy/server/services/registry";
import type { InferResultType } from "@dokploy/server/types/with"; import type { InferResultType } from "@dokploy/server/types/with";
import type { CreateServiceOptions } from "dockerode"; import type { CreateServiceOptions } from "dockerode";
import { getRegistryTag, uploadImageRemoteCommand } from "../cluster/upload"; import { getRegistryTag, uploadImageRemoteCommand } from "../cluster/upload";
@@ -28,9 +29,9 @@ export type ApplicationNested = InferResultType<
security: true; security: true;
redirects: true; redirects: true;
ports: true; ports: true;
registry: true; registry: { columns: { password: false } };
buildRegistry: true; buildRegistry: { columns: { password: false } };
rollbackRegistry: true; rollbackRegistry: { columns: { password: false } };
deployments: true; deployments: true;
environment: { with: { project: true } }; environment: { with: { project: true } };
} }
@@ -121,8 +122,8 @@ export const mechanizeDockerContainer = async (
application.environment.env, application.environment.env,
); );
const image = getImageName(application); const image = await getImageName(application);
const authConfig = getAuthConfig(application); const authConfig = await getAuthConfig(application);
const docker = await getRemoteDocker(application.serverId); const docker = await getRemoteDocker(application.serverId);
const settings: CreateServiceOptions = { const settings: CreateServiceOptions = {
@@ -190,7 +191,7 @@ export const mechanizeDockerContainer = async (
} }
}; };
const getImageName = (application: ApplicationNested) => { const getImageName = async (application: ApplicationNested) => {
const { appName, sourceType, dockerImage, registry, buildRegistry } = const { appName, sourceType, dockerImage, registry, buildRegistry } =
application; application;
const imageName = `${appName}:latest`; const imageName = `${appName}:latest`;
@@ -199,18 +200,18 @@ const getImageName = (application: ApplicationNested) => {
} }
if (registry) { if (registry) {
const registryTag = getRegistryTag(registry, imageName); const r = await findRegistryByIdWithCredentials(registry.registryId);
return registryTag; return getRegistryTag(r, imageName);
} }
if (buildRegistry) { if (buildRegistry) {
const registryTag = getRegistryTag(buildRegistry, imageName); const r = await findRegistryByIdWithCredentials(buildRegistry.registryId);
return registryTag; return getRegistryTag(r, imageName);
} }
return imageName; return imageName;
}; };
export const getAuthConfig = (application: ApplicationNested) => { export const getAuthConfig = async (application: ApplicationNested) => {
const { const {
registry, registry,
buildRegistry, buildRegistry,
@@ -222,23 +223,21 @@ export const getAuthConfig = (application: ApplicationNested) => {
if (sourceType === "docker") { if (sourceType === "docker") {
if (username && password) { if (username && password) {
return { return { password, username, serveraddress: registryUrl || "" };
password,
username,
serveraddress: registryUrl || "",
};
} }
} else if (registry) { } else if (registry) {
const r = await findRegistryByIdWithCredentials(registry.registryId);
return { return {
password: registry.password, password: r.password,
username: registry.username, username: r.username,
serveraddress: registry.registryUrl, serveraddress: r.registryUrl,
}; };
} else if (buildRegistry) { } else if (buildRegistry) {
const r = await findRegistryByIdWithCredentials(buildRegistry.registryId);
return { return {
password: buildRegistry.password, password: r.password,
username: buildRegistry.username, username: r.username,
serveraddress: buildRegistry.registryUrl, serveraddress: r.registryUrl,
}; };
} }
+18 -16
View File
@@ -1,5 +1,8 @@
import { findAllDeploymentsByApplicationId } from "@dokploy/server/services/deployment"; import { findAllDeploymentsByApplicationId } from "@dokploy/server/services/deployment";
import type { Registry } from "@dokploy/server/services/registry"; import {
findRegistryByIdWithCredentials,
type Registry,
} from "@dokploy/server/services/registry";
import { createRollback } from "@dokploy/server/services/rollbacks"; import { createRollback } from "@dokploy/server/services/rollbacks";
import type { ApplicationNested } from "../builders"; import type { ApplicationNested } from "../builders";
@@ -22,19 +25,19 @@ export const uploadImageRemoteCommand = async (
const commands: string[] = []; const commands: string[] = [];
if (registry) { if (registry) {
const registryTag = getRegistryTag(registry, imageName); const r = await findRegistryByIdWithCredentials(registry.registryId);
const registryTag = getRegistryTag(r, imageName);
if (registryTag) { if (registryTag) {
commands.push(`echo "📦 [Enabled Registry Swarm]"`); commands.push(`echo "📦 [Enabled Registry Swarm]"`);
commands.push(getRegistryCommands(registry, imageName, registryTag)); commands.push(getRegistryCommands(r, imageName, registryTag));
} }
} }
if (buildRegistry) { if (buildRegistry) {
const buildRegistryTag = getRegistryTag(buildRegistry, imageName); const r = await findRegistryByIdWithCredentials(buildRegistry.registryId);
const buildRegistryTag = getRegistryTag(r, imageName);
if (buildRegistryTag) { if (buildRegistryTag) {
commands.push(`echo "🔑 [Enabled Build Registry]"`); commands.push(`echo "🔑 [Enabled Build Registry]"`);
commands.push( commands.push(getRegistryCommands(r, imageName, buildRegistryTag));
getRegistryCommands(buildRegistry, imageName, buildRegistryTag),
);
commands.push( commands.push(
`echo "⚠️ INFO: After the build is finished, you need to wait a few seconds for the server to download the image and run the container."`, `echo "⚠️ INFO: After the build is finished, you need to wait a few seconds for the server to download the image and run the container."`,
); );
@@ -57,15 +60,13 @@ export const uploadImageRemoteCommand = async (
deploymentId: deploymentId, deploymentId: deploymentId,
}); });
const rollbackRegistryTag = getRegistryTag( const r = await findRegistryByIdWithCredentials(
rollbackRegistry, rollbackRegistry.registryId,
rollback?.image || "",
); );
const rollbackRegistryTag = getRegistryTag(r, rollback?.image || "");
if (rollbackRegistryTag) { if (rollbackRegistryTag) {
commands.push(`echo "🔄 [Enabled Rollback Registry]"`); commands.push(`echo "🔄 [Enabled Rollback Registry]"`);
commands.push( commands.push(getRegistryCommands(r, imageName, rollbackRegistryTag));
getRegistryCommands(rollbackRegistry, imageName, rollbackRegistryTag),
);
} }
} }
try { try {
@@ -74,6 +75,7 @@ export const uploadImageRemoteCommand = async (
throw error; throw error;
} }
}; };
/** /**
* Extract the repository name from imageName by taking the last part after '/' * Extract the repository name from imageName by taking the last part after '/'
* Examples: * Examples:
@@ -117,17 +119,17 @@ const getRegistryCommands = (
): string => { ): string => {
return ` return `
echo "📦 [Enabled Registry] Uploading image to '${registry.registryType}' | '${registryTag}'" ; echo "📦 [Enabled Registry] Uploading image to '${registry.registryType}' | '${registryTag}'" ;
echo "${registry.password}" | docker login ${registry.registryUrl} -u '${registry.username}' --password-stdin || { echo "${registry.password}" | docker login ${registry.registryUrl} -u '${registry.username}' --password-stdin || {
echo "❌ DockerHub Failed" ; echo "❌ DockerHub Failed" ;
exit 1; exit 1;
} }
echo "✅ Registry Login Success" ; echo "✅ Registry Login Success" ;
docker tag ${imageName} ${registryTag} || { docker tag ${imageName} ${registryTag} || {
echo "❌ Error tagging image" ; echo "❌ Error tagging image" ;
exit 1; exit 1;
} }
echo "✅ Image Tagged" ; echo "✅ Image Tagged" ;
docker push ${registryTag} || { docker push ${registryTag} || {
echo "❌ Error pushing image" ; echo "❌ Error pushing image" ;
exit 1; exit 1;
} }
@@ -1,6 +1,7 @@
import path from "node:path"; import path from "node:path";
import { paths } from "@dokploy/server/constants"; import { paths } from "@dokploy/server/constants";
import { findComposeById } from "@dokploy/server/services/compose"; import { findComposeById } from "@dokploy/server/services/compose";
import { findDestinationById } from "@dokploy/server/services/destination";
import type { findVolumeBackupById } from "@dokploy/server/services/volume-backups"; import type { findVolumeBackupById } from "@dokploy/server/services/volume-backups";
import { import {
getBackupTimestamp, getBackupTimestamp,
@@ -31,14 +32,14 @@ export const backupVolume = async (
volumeBackup: Awaited<ReturnType<typeof findVolumeBackupById>>, volumeBackup: Awaited<ReturnType<typeof findVolumeBackupById>>,
) => { ) => {
const { serviceType, volumeName, turnOff, prefix } = volumeBackup; const { serviceType, volumeName, turnOff, prefix } = volumeBackup;
const destination = await findDestinationById(volumeBackup.destinationId);
const serverId = const serverId =
volumeBackup.application?.serverId || volumeBackup.compose?.serverId; volumeBackup.application?.serverId || volumeBackup.compose?.serverId;
const { VOLUME_BACKUPS_PATH, VOLUME_BACKUP_LOCK_PATH } = paths(!!serverId); const { VOLUME_BACKUPS_PATH, VOLUME_BACKUP_LOCK_PATH } = paths(!!serverId);
const destination = volumeBackup.destination;
const s3AppName = getVolumeServiceAppName(volumeBackup); const s3AppName = getVolumeServiceAppName(volumeBackup);
const backupFileName = `${volumeName}-${getBackupTimestamp()}.tar`; const backupFileName = `${volumeName}-${getBackupTimestamp()}.tar`;
const bucketDestination = `${s3AppName}/${normalizeS3Path(prefix || "")}${backupFileName}`; const bucketDestination = `${s3AppName}/${normalizeS3Path(prefix || "")}${backupFileName}`;
const rcloneFlags = getS3Credentials(volumeBackup.destination); const rcloneFlags = getS3Credentials(destination);
const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`; const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`;
const volumeBackupPath = path.join(VOLUME_BACKUPS_PATH, volumeBackup.appName); const volumeBackupPath = path.join(VOLUME_BACKUPS_PATH, volumeBackup.appName);
@@ -4,6 +4,7 @@ import {
createDeploymentVolumeBackup, createDeploymentVolumeBackup,
updateDeploymentStatus, updateDeploymentStatus,
} from "@dokploy/server/services/deployment"; } from "@dokploy/server/services/deployment";
import { findDestinationById } from "@dokploy/server/services/destination";
import { findVolumeBackupById } from "@dokploy/server/services/volume-backups"; import { findVolumeBackupById } from "@dokploy/server/services/volume-backups";
import { import {
execAsync, execAsync,
@@ -77,7 +78,8 @@ const cleanupOldVolumeBackups = async (
volumeBackup: Awaited<ReturnType<typeof findVolumeBackupById>>, volumeBackup: Awaited<ReturnType<typeof findVolumeBackupById>>,
serverId?: string | null, serverId?: string | null,
) => { ) => {
const { keepLatestCount, destination, prefix, volumeName } = volumeBackup; const { keepLatestCount, prefix, volumeName } = volumeBackup;
const destination = await findDestinationById(volumeBackup.destinationId);
if (!keepLatestCount) return; if (!keepLatestCount) return;