Merge remote-tracking branch 'origin/canary' into fix/env-update-issue

# Conflicts:
#	packages/server/src/utils/builders/railpack.ts
This commit is contained in:
Mauricio Siu
2026-08-03 03:53:41 -06:00
550 changed files with 127113 additions and 34784 deletions
+15 -4
View File
@@ -67,9 +67,20 @@ Compose Type: ${composeType} ✅`;
return bashCommand;
};
// Shell control characters that must never appear in a user-provided compose
// command: they would let it break out of the `docker ${command}` invocation
// into arbitrary host commands. A normal docker compose CLI line never needs them.
const UNSAFE_COMPOSE_COMMAND = /[;&|`$(){}<>\n\\]/;
const sanitizeCommand = (command: string) => {
const sanitizedCommand = command.trim();
if (UNSAFE_COMPOSE_COMMAND.test(sanitizedCommand)) {
throw new Error(
"Invalid characters in compose command: shell control characters are not allowed",
);
}
const parts = sanitizedCommand.split(/\s+/);
const restCommand = parts.map((arg) => arg.replace(/^"(.*)"$/, "$1"));
@@ -88,9 +99,9 @@ export const createCommand = (compose: ComposeNested) => {
let command = "";
if (composeType === "docker-compose") {
command = `compose -p ${appName} -f ${path} up -d --build --remove-orphans`;
command = `compose -p ${quote([appName])} -f ${quote([path])} up -d --build --remove-orphans`;
} else if (composeType === "stack") {
command = `stack deploy -c ${path} ${appName} --prune --with-registry-auth`;
command = `stack deploy -c ${quote([path])} ${quote([appName])} --prune --with-registry-auth`;
}
return command;
@@ -124,8 +135,8 @@ export const getCreateEnvFileCommand = (compose: ComposeNested) => {
const encodedContent = encodeBase64(envFileContent);
return `
touch ${envFilePath};
echo "${encodedContent}" | base64 -d > "${envFilePath}";
touch ${quote([envFilePath])};
echo "${encodedContent}" | base64 -d > ${quote([envFilePath])};
`;
};
@@ -85,9 +85,9 @@ export const getDockerCommand = (application: ApplicationNested) => {
}
command += `
echo "Building ${appName}" ;
cd ${dockerContextPath} || {
echo "❌ The path ${dockerContextPath} does not exist" ;
echo ${quote([`Building ${appName}`])} ;
cd ${quote([dockerContextPath])} || {
echo ${quote([`❌ The path ${dockerContextPath} does not exist`])} ;
exit 1;
}
+25 -24
View File
@@ -1,3 +1,5 @@
import { resolveServiceNetworks } from "@dokploy/server/services/network";
import { findRegistryByIdWithCredentials } from "@dokploy/server/services/registry";
import type { InferResultType } from "@dokploy/server/types/with";
import type { CreateServiceOptions } from "dockerode";
import { getRegistryTag, uploadImageRemoteCommand } from "../cluster/upload";
@@ -28,9 +30,9 @@ export type ApplicationNested = InferResultType<
security: true;
redirects: true;
ports: true;
registry: true;
buildRegistry: true;
rollbackRegistry: true;
registry: { columns: { password: false } };
buildRegistry: { columns: { password: false } };
rollbackRegistry: { columns: { password: false } };
deployments: true;
environment: { with: { project: true } };
}
@@ -99,6 +101,8 @@ export const mechanizeDockerContainer = async (
const volumesMount = generateVolumeMounts(mounts);
const resolvedNetworks = await resolveServiceNetworks(application);
const {
HealthCheck,
RestartPolicy,
@@ -107,7 +111,6 @@ export const mechanizeDockerContainer = async (
Mode,
RollbackConfig,
UpdateConfig,
Networks,
StopGracePeriod,
EndpointSpec,
Ulimits,
@@ -121,8 +124,8 @@ export const mechanizeDockerContainer = async (
application.environment.env,
);
const image = getImageName(application);
const authConfig = getAuthConfig(application);
const image = await getImageName(application);
const authConfig = await getAuthConfig(application);
const docker = await getRemoteDocker(application.serverId);
const settings: CreateServiceOptions = {
@@ -146,7 +149,7 @@ export const mechanizeDockerContainer = async (
...(Ulimits && { Ulimits }),
Labels,
},
Networks,
Networks: resolvedNetworks,
RestartPolicy,
Placement,
Resources: {
@@ -190,7 +193,7 @@ export const mechanizeDockerContainer = async (
}
};
const getImageName = (application: ApplicationNested) => {
const getImageName = async (application: ApplicationNested) => {
const { appName, sourceType, dockerImage, registry, buildRegistry } =
application;
const imageName = `${appName}:latest`;
@@ -199,18 +202,18 @@ const getImageName = (application: ApplicationNested) => {
}
if (registry) {
const registryTag = getRegistryTag(registry, imageName);
return registryTag;
const r = await findRegistryByIdWithCredentials(registry.registryId);
return getRegistryTag(r, imageName);
}
if (buildRegistry) {
const registryTag = getRegistryTag(buildRegistry, imageName);
return registryTag;
const r = await findRegistryByIdWithCredentials(buildRegistry.registryId);
return getRegistryTag(r, imageName);
}
return imageName;
};
export const getAuthConfig = (application: ApplicationNested) => {
export const getAuthConfig = async (application: ApplicationNested) => {
const {
registry,
buildRegistry,
@@ -222,23 +225,21 @@ export const getAuthConfig = (application: ApplicationNested) => {
if (sourceType === "docker") {
if (username && password) {
return {
password,
username,
serveraddress: registryUrl || "",
};
return { password, username, serveraddress: registryUrl || "" };
}
} else if (registry) {
const r = await findRegistryByIdWithCredentials(registry.registryId);
return {
password: registry.password,
username: registry.username,
serveraddress: registry.registryUrl,
password: r.password,
username: r.username,
serveraddress: r.registryUrl,
};
} else if (buildRegistry) {
const r = await findRegistryByIdWithCredentials(buildRegistry.registryId);
return {
password: buildRegistry.password,
username: buildRegistry.username,
serveraddress: buildRegistry.registryUrl,
password: r.password,
username: r.username,
serveraddress: r.registryUrl,
};
}
@@ -1,6 +1,7 @@
import path from "node:path";
import { getStaticCommand } from "@dokploy/server/utils/builders/static";
import { nanoid } from "nanoid";
import { quote } from "shell-quote";
import { prepareEnvironmentVariablesForShell } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory";
import type { ApplicationNested } from ".";
@@ -52,10 +53,10 @@ export const getNixpacksCommand = (application: ApplicationNested) => {
bashCommand += `
docker create --name ${buildContainerId} ${appName}
mkdir -p ${localPath}
docker cp ${buildContainerId}:/app/${publishDirectory}${isDirectory ? "/." : ""} ${path.join(buildAppDirectory, publishDirectory)} || {
mkdir -p ${quote([localPath])}
docker cp ${quote([`${buildContainerId}:/app/${publishDirectory}${isDirectory ? "/." : ""}`])} ${quote([localPath])} || {
docker rm ${buildContainerId}
echo "❌ Copying ${publishDirectory} to ${path.join(buildAppDirectory, publishDirectory)} failed" ;
echo ${quote([`❌ Copying ${publishDirectory} to ${localPath} failed`])} ;
exit 1;
}
docker rm ${buildContainerId}
+13 -9
View File
@@ -44,10 +44,15 @@ export const getRailpackCommand = (application: ApplicationNested) => {
const secretsHash = calculateSecretsHash(envVariables);
const cacheKey = cleanCache ? nanoid(10) : undefined;
// Build command
// Build command.
// Use a unique builder name per build so concurrent deployments don't race
// on a shared "builder-containerd" instance (create/use/rm collisions).
const builderName = `railpack-${appName}-${nanoid(6)}`;
const buildArgs = [
"buildx",
"build",
"--builder",
builderName,
"--build-arg",
`secrets-hash=${secretsHash}`,
...(cacheKey ? ["--build-arg", `cache-key=${cacheKey}`] : []),
@@ -79,17 +84,16 @@ export const getRailpackCommand = (application: ApplicationNested) => {
const bashCommand = `
# Ensure we have a builder with containerd
# Ensure we have a builder with containerd (isolated per build)
export RAILPACK_VERSION=${application.railpackVersion}
bash -c "$(curl -fsSL https://railpack.com/install.sh)"
docker buildx create --use --name builder-containerd --driver docker-container || true
docker buildx use builder-containerd
docker buildx create --name ${builderName} --driver docker-container || true
echo "Preparing Railpack build plan..." ;
railpack ${prepareArgs.join(" ")} || {
railpack ${prepareArgs.join(" ")} || {
echo "❌ Railpack prepare failed" ;
docker buildx rm builder-containerd || true
docker buildx rm ${builderName} || true
exit 1;
}
echo "✅ Railpack prepare completed." ;
@@ -97,13 +101,13 @@ echo "✅ Railpack prepare completed." ;
echo "Building with Railpack frontend..." ;
# Export environment variables for secrets
${exportEnvs.join("\n")}
docker ${buildArgs.join(" ")} || {
docker ${buildArgs.join(" ")} || {
echo "❌ Railpack build failed" ;
docker buildx rm builder-containerd || true
docker buildx rm ${builderName} || true
exit 1;
}
echo "✅ Railpack build completed." ;
docker buildx rm builder-containerd
docker buildx rm ${builderName} || true
`;
return bashCommand;