feat(networks): enhance network management and UI components

- Added functionality for assigning Docker networks to services, allowing users to manage network associations directly from the UI.
- Introduced new components for assigning networks to both individual services and compose files, improving user experience and flexibility.
- Updated existing components to integrate network assignment features, ensuring seamless interaction within the dashboard.
- Enhanced the database schema to support new network-related fields, including `networkIds` and `detachDokployNetwork`, for better service configuration.
- Improved the layout and responsiveness of various UI elements to accommodate new features and enhance usability.
This commit is contained in:
Mauricio Siu
2026-07-25 03:09:54 -06:00
parent 8a37626636
commit 97885ee44a
57 changed files with 1382 additions and 307 deletions

View File

@@ -234,6 +234,9 @@ export const applications = pgTable("application", {
},
),
networkIds: text("networkIds").array().default([]),
detachDokployNetwork: boolean("detachDokployNetwork")
.notNull()
.default(false),
});
export const applicationsRelations = relations(
@@ -376,6 +379,7 @@ const createSchema = createInsertSchema(applications, {
watchPaths: z.array(z.string()).optional().optional(),
previewLabels: z.array(z.string()).optional(),
networkIds: z.array(z.string()).optional(),
detachDokployNetwork: z.boolean().optional(),
cleanCache: z.boolean().optional(),
stopGracePeriodSwarm: z.number().nullable(),
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),

View File

@@ -1,5 +1,12 @@
import { relations } from "drizzle-orm";
import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import {
boolean,
integer,
jsonb,
pgEnum,
pgTable,
text,
} from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
@@ -113,7 +120,15 @@ export const compose = pgTable("compose", {
serverId: text("serverId").references(() => server.serverId, {
onDelete: "cascade",
}),
networkIds: text("networkIds").array().default([]),
serviceNetworks: jsonb("serviceNetworks")
.$type<
Array<{
serviceName: string;
networkIds: string[];
detachDokployNetwork: boolean;
}>
>()
.default([]),
});
export const composeRelations = relations(compose, ({ one, many }) => ({
@@ -175,6 +190,15 @@ const createSchema = createInsertSchema(compose, {
.optional(),
triggerType: z.enum(["push", "tag"]).optional(),
composeStatus: z.enum(["idle", "running", "done", "error"]).optional(),
serviceNetworks: z
.array(
z.object({
serviceName: z.string(),
networkIds: z.array(z.string()),
detachDokployNetwork: z.boolean(),
}),
)
.optional(),
});
export const apiCreateCompose = createSchema.pick({

View File

@@ -19,8 +19,8 @@ export * from "./libsql";
export * from "./mariadb";
export * from "./mongo";
export * from "./mount";
export * from "./network";
export * from "./mysql";
export * from "./network";
export * from "./notification";
export * from "./patch";
export * from "./port";

View File

@@ -83,6 +83,10 @@ export const libsql = pgTable("libsql", {
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "number" }),
endpointSpecSwarm: json("endpointSpecSwarm").$type<EndpointSpecSwarm>(),
replicas: integer("replicas").default(1).notNull(),
networkIds: text("networkIds").array().default([]),
detachDokployNetwork: boolean("detachDokployNetwork")
.notNull()
.default(false),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
@@ -146,6 +150,8 @@ const createSchema = createInsertSchema(libsql, {
networkSwarm: NetworkSwarmSchema.nullable(),
stopGracePeriodSwarm: z.number().nullable(),
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
networkIds: z.array(z.string()).optional(),
detachDokployNetwork: z.boolean().optional(),
});
export const apiCreateLibsql = createSchema

View File

@@ -1,5 +1,12 @@
import { relations } from "drizzle-orm";
import { bigint, integer, json, pgTable, text } from "drizzle-orm/pg-core";
import {
bigint,
boolean,
integer,
json,
pgTable,
text,
} from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
@@ -89,6 +96,9 @@ export const mariadb = pgTable("mariadb", {
onDelete: "cascade",
}),
networkIds: text("networkIds").array().default([]),
detachDokployNetwork: boolean("detachDokployNetwork")
.notNull()
.default(false),
});
export const mariadbRelations = relations(mariadb, ({ one, many }) => ({
@@ -149,6 +159,8 @@ const createSchema = createInsertSchema(mariadb, {
stopGracePeriodSwarm: z.number().nullable(),
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
networkIds: z.array(z.string()).optional(),
detachDokployNetwork: z.boolean().optional(),
});
export const apiCreateMariaDB = createSchema.pick({

View File

@@ -93,6 +93,9 @@ export const mongo = pgTable("mongo", {
}),
replicaSets: boolean("replicaSets").default(false),
networkIds: text("networkIds").array().default([]),
detachDokployNetwork: boolean("detachDokployNetwork")
.notNull()
.default(false),
});
export const mongoRelations = relations(mongo, ({ one, many }) => ({
@@ -147,6 +150,8 @@ const createSchema = createInsertSchema(mongo, {
stopGracePeriodSwarm: z.number().nullable(),
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
networkIds: z.array(z.string()).optional(),
detachDokployNetwork: z.boolean().optional(),
});
export const apiCreateMongo = createSchema.pick({

View File

@@ -1,5 +1,12 @@
import { relations } from "drizzle-orm";
import { bigint, integer, json, pgTable, text } from "drizzle-orm/pg-core";
import {
bigint,
boolean,
integer,
json,
pgTable,
text,
} from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
@@ -87,6 +94,9 @@ export const mysql = pgTable("mysql", {
onDelete: "cascade",
}),
networkIds: text("networkIds").array().default([]),
detachDokployNetwork: boolean("detachDokployNetwork")
.notNull()
.default(false),
});
export const mysqlRelations = relations(mysql, ({ one, many }) => ({
@@ -146,6 +156,8 @@ const createSchema = createInsertSchema(mysql, {
stopGracePeriodSwarm: z.number().nullable(),
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
networkIds: z.array(z.string()).optional(),
detachDokployNetwork: z.boolean().optional(),
});
export const apiCreateMySql = createSchema.pick({

View File

@@ -1,5 +1,12 @@
import { relations } from "drizzle-orm";
import { bigint, integer, json, pgTable, text } from "drizzle-orm/pg-core";
import {
bigint,
boolean,
integer,
json,
pgTable,
text,
} from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
@@ -87,6 +94,9 @@ export const postgres = pgTable("postgres", {
onDelete: "cascade",
}),
networkIds: text("networkIds").array().default([]),
detachDokployNetwork: boolean("detachDokployNetwork")
.notNull()
.default(false),
});
export const postgresRelations = relations(postgres, ({ one, many }) => ({
@@ -141,6 +151,8 @@ const createSchema = createInsertSchema(postgres, {
stopGracePeriodSwarm: z.number().nullable(),
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
networkIds: z.array(z.string()).optional(),
detachDokployNetwork: z.boolean().optional(),
});
export const apiCreatePostgres = createSchema.pick({

View File

@@ -1,5 +1,12 @@
import { relations } from "drizzle-orm";
import { bigint, integer, json, pgTable, text } from "drizzle-orm/pg-core";
import {
bigint,
boolean,
integer,
json,
pgTable,
text,
} from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
@@ -81,6 +88,9 @@ export const redis = pgTable("redis", {
onDelete: "cascade",
}),
networkIds: text("networkIds").array().default([]),
detachDokployNetwork: boolean("detachDokployNetwork")
.notNull()
.default(false),
});
export const redisRelations = relations(redis, ({ one, many }) => ({
@@ -130,6 +140,8 @@ const createSchema = createInsertSchema(redis, {
stopGracePeriodSwarm: z.number().nullable(),
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
networkIds: z.array(z.string()).optional(),
detachDokployNetwork: z.boolean().optional(),
});
export const apiCreateRedis = createSchema.pick({

View File

@@ -83,8 +83,17 @@ export const findDeploymentById = async (deploymentId: string) => {
const deployment = await db.query.deployments.findFirst({
where: eq(deployments.deploymentId, deploymentId),
with: {
application: true,
compose: true,
application: {
columns: {
applicationId: true,
appName: true,
name: true,
serverId: true,
},
},
compose: {
columns: { composeId: true, appName: true, name: true, serverId: true },
},
schedule: true,
},
});

View File

@@ -80,7 +80,9 @@ export const findDomainById = async (domainId: string) => {
const domain = await db.query.domains.findFirst({
where: eq(domains.domainId, domainId),
with: {
application: true,
application: {
columns: { applicationId: true, appName: true, name: true },
},
},
});
if (!domain) {
@@ -96,7 +98,9 @@ export const findDomainsByApplicationId = async (applicationId: string) => {
const domainsArray = await db.query.domains.findMany({
where: eq(domains.applicationId, applicationId),
with: {
application: true,
application: {
columns: { applicationId: true, appName: true, name: true },
},
},
});
@@ -107,7 +111,9 @@ export const findDomainsByComposeId = async (composeId: string) => {
const domainsArray = await db.query.domains.findMany({
where: eq(domains.composeId, composeId),
with: {
compose: true,
compose: {
columns: { composeId: true, appName: true, name: true },
},
},
});

View File

@@ -201,18 +201,51 @@ export const findEnvironmentById = async (environmentId: string) => {
};
export const findEnvironmentsByProjectId = async (projectId: string) => {
const serviceColumns = {
name: true,
description: true,
appName: true,
createdAt: true,
serverId: true,
applicationStatus: true,
} as const;
const projectEnvironments = await db.query.environments.findMany({
where: eq(environments.projectId, projectId),
orderBy: asc(environments.createdAt),
with: {
applications: true,
mariadb: true,
mongo: true,
mysql: true,
postgres: true,
redis: true,
compose: true,
libsql: true,
applications: {
columns: { ...serviceColumns, applicationId: true, icon: true },
with: { server: { columns: { name: true } } },
},
mariadb: {
columns: { ...serviceColumns, mariadbId: true },
with: { server: { columns: { name: true } } },
},
mongo: {
columns: { ...serviceColumns, mongoId: true },
with: { server: { columns: { name: true } } },
},
mysql: {
columns: { ...serviceColumns, mysqlId: true },
with: { server: { columns: { name: true } } },
},
postgres: {
columns: { ...serviceColumns, postgresId: true },
with: { server: { columns: { name: true } } },
},
redis: {
columns: { ...serviceColumns, redisId: true },
with: { server: { columns: { name: true } } },
},
compose: {
columns: { ...serviceColumns, composeId: true, composeStatus: true },
with: { server: { columns: { name: true } } },
},
libsql: {
columns: { ...serviceColumns, libsqlId: true },
with: { server: { columns: { name: true } } },
},
project: true,
},
columns: {

View File

@@ -107,81 +107,29 @@ export const createFileMount = async (mountId: string) => {
};
export const findMountById = async (mountId: string) => {
const serviceWith = {
columns: { serverId: true, appName: true },
with: {
environment: {
columns: {},
with: {
project: { columns: { organizationId: true } },
},
},
},
} as const;
const mount = await db.query.mounts.findFirst({
where: eq(mounts.mountId, mountId),
with: {
application: {
with: {
environment: {
with: {
project: true,
},
},
},
},
compose: {
with: {
environment: {
with: {
project: true,
},
},
},
},
libsql: {
with: {
environment: {
with: {
project: true,
},
},
},
},
mariadb: {
with: {
environment: {
with: {
project: true,
},
},
},
},
mongo: {
with: {
environment: {
with: {
project: true,
},
},
},
},
mysql: {
with: {
environment: {
with: {
project: true,
},
},
},
},
postgres: {
with: {
environment: {
with: {
project: true,
},
},
},
},
redis: {
with: {
environment: {
with: {
project: true,
},
},
},
},
application: serviceWith,
compose: serviceWith,
libsql: serviceWith,
mariadb: serviceWith,
mongo: serviceWith,
mysql: serviceWith,
postgres: serviceWith,
redis: serviceWith,
},
});
if (!mount) {

View File

@@ -1,9 +1,10 @@
import { db } from "@dokploy/server/db";
import { type apiCreateNetwork, network } from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
import { and, eq, isNull } from "drizzle-orm";
import { and, eq, inArray, isNull } from "drizzle-orm";
import type { z } from "zod";
import { IS_CLOUD } from "../constants";
import type { ApplicationNested } from "../utils/builders";
import { getRemoteDocker } from "../utils/servers/remote-docker";
// Networks managed by Docker/Dokploy itself that must never be imported
@@ -273,14 +274,39 @@ const createDockerNetworkFromRow = async (row: typeof network.$inferSelect) => {
}
};
// Re-creates the Docker network from the stored record, for records whose
// network was removed from Docker outside of Dokploy
export const recreateNetwork = async (networkId: string) => {
const row = await findNetworkById(networkId);
await createDockerNetworkFromRow(row);
return row;
};
export const resolveServiceNetworks = async (
application: Partial<ApplicationNested>,
) => {
if (application.networkSwarm) {
return application.networkSwarm;
}
const { networkIds, detachDokployNetwork } = application;
const rows =
networkIds && networkIds.length > 0
? await db.query.network.findMany({
where: and(
inArray(network.networkId, networkIds),
eq(network.driver, "overlay"),
),
columns: { name: true },
})
: [];
const networks = detachDokployNetwork ? [] : ["dokploy-network"];
for (const row of rows) {
networks.push(row.name);
}
return networks.map((name) => ({ Target: name }));
};
export const inspectNetwork = async (networkId: string) => {
const row = await findNetworkById(networkId);

View File

@@ -48,19 +48,60 @@ export const createProject = async (
};
export const findProjectById = async (projectId: string) => {
const serviceColumns = {
name: true,
description: true,
appName: true,
createdAt: true,
serverId: true,
applicationStatus: true,
} as const;
const project = await db.query.projects.findFirst({
where: eq(projects.projectId, projectId),
with: {
environments: {
with: {
applications: true,
compose: true,
libsql: true,
mariadb: true,
mongo: true,
mysql: true,
postgres: true,
redis: true,
applications: {
columns: {
...serviceColumns,
applicationId: true,
icon: true,
},
with: { server: { columns: { name: true } } },
},
compose: {
columns: {
...serviceColumns,
composeId: true,
composeStatus: true,
},
with: { server: { columns: { name: true } } },
},
libsql: {
columns: { ...serviceColumns, libsqlId: true },
with: { server: { columns: { name: true } } },
},
mariadb: {
columns: { ...serviceColumns, mariadbId: true },
with: { server: { columns: { name: true } } },
},
mongo: {
columns: { ...serviceColumns, mongoId: true },
with: { server: { columns: { name: true } } },
},
mysql: {
columns: { ...serviceColumns, mysqlId: true },
with: { server: { columns: { name: true } } },
},
postgres: {
columns: { ...serviceColumns, postgresId: true },
with: { server: { columns: { name: true } } },
},
redis: {
columns: { ...serviceColumns, redisId: true },
with: { server: { columns: { name: true } } },
},
},
},
projectTags: {
@@ -107,27 +148,49 @@ export const updateProjectById = async (
export const validUniqueServerAppName = async (appName: string) => {
const query = await db.query.environments.findMany({
columns: { environmentId: true },
with: {
applications: {
where: eq(applications.appName, appName),
columns: {
appName: true,
},
},
libsql: {
where: eq(libsql.appName, appName),
columns: {
appName: true,
},
},
mariadb: {
where: eq(mariadb.appName, appName),
columns: {
appName: true,
},
},
mongo: {
where: eq(mongo.appName, appName),
columns: {
appName: true,
},
},
mysql: {
where: eq(mysql.appName, appName),
columns: {
appName: true,
},
},
postgres: {
where: eq(postgres.appName, appName),
columns: {
appName: true,
},
},
redis: {
where: eq(redis.appName, appName),
columns: {
appName: true,
},
},
},
});

View File

@@ -20,6 +20,7 @@ import { getRemoteDocker } from "../utils/servers/remote-docker";
import { type Application, findApplicationById } from "./application";
import { findDeploymentById } from "./deployment";
import type { Mount } from "./mount";
import { resolveServiceNetworks } from "./network";
import type { Port } from "./port";
import type { Project } from "./project";
import {
@@ -257,6 +258,10 @@ const rollbackApplication = async (
const volumesMount = generateVolumeMounts(mounts);
const resolvedNetworks = await resolveServiceNetworks(
fullContext as Parameters<typeof resolveServiceNetworks>[0],
);
const {
HealthCheck,
RestartPolicy,
@@ -265,7 +270,6 @@ const rollbackApplication = async (
Mode,
RollbackConfig,
UpdateConfig,
Networks,
Ulimits,
} = generateConfigContainer(
fullContext as Parameters<typeof generateConfigContainer>[0],
@@ -304,7 +308,7 @@ const rollbackApplication = async (
...(Ulimits && { Ulimits }),
Labels,
},
Networks,
Networks: resolvedNetworks,
RestartPolicy,
Placement,
Resources: {

View File

@@ -100,15 +100,16 @@ export const deleteServer = async (serverId: string) => {
export const haveActiveServices = async (serverId: string) => {
const currentServer = await db.query.server.findFirst({
where: eq(server.serverId, serverId),
columns: { serverId: true },
with: {
applications: true,
compose: true,
libsql: true,
mariadb: true,
mongo: true,
mysql: true,
postgres: true,
redis: true,
applications: { columns: { applicationId: true } },
compose: { columns: { composeId: true } },
libsql: { columns: { libsqlId: true } },
mariadb: { columns: { mariadbId: true } },
mongo: { columns: { mongoId: true } },
mysql: { columns: { mysqlId: true } },
postgres: { columns: { postgresId: true } },
redis: { columns: { redisId: true } },
},
});

View File

@@ -35,7 +35,8 @@ export const generateRandomDomain = ({
projectName,
}: Schema): string => {
const hash = randomBytes(3).toString("hex");
const slugIp = serverIp.replaceAll(".", "-").replaceAll(":", "-");
const effectiveIp = serverIp || "127.0.0.1";
const slugIp = effectiveIp.replaceAll(".", "-").replaceAll(":", "-");
// Domain labels have a max length of 63 characters
// Reserve space for: hash (6) + separators (1-2) + ip section + dot + sslip.io (8)
@@ -46,7 +47,7 @@ export const generateRandomDomain = ({
? projectName.substring(0, maxProjectNameLength)
: projectName;
return `${truncatedProjectName}-${hash}${slugIp === "" ? "" : `-${slugIp}`}.sslip.io`;
return `${truncatedProjectName}-${hash}-${slugIp}.sslip.io`;
};
export const generateHash = (length = 8): string => {

View File

@@ -1,3 +1,4 @@
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";
@@ -100,6 +101,8 @@ export const mechanizeDockerContainer = async (
const volumesMount = generateVolumeMounts(mounts);
const resolvedNetworks = await resolveServiceNetworks(application);
const {
HealthCheck,
RestartPolicy,
@@ -108,7 +111,6 @@ export const mechanizeDockerContainer = async (
Mode,
RollbackConfig,
UpdateConfig,
Networks,
StopGracePeriod,
EndpointSpec,
Ulimits,
@@ -147,7 +149,7 @@ export const mechanizeDockerContainer = async (
...(Ulimits && { Ulimits }),
Labels,
},
Networks,
Networks: resolvedNetworks,
RestartPolicy,
Placement,
Resources: {

View File

@@ -1,5 +1,6 @@
import type { InferResultType } from "@dokploy/server/types/with";
import type { CreateServiceOptions, PortConfig } from "dockerode";
import { resolveServiceNetworks } from "../../services/network";
import {
calculateResources,
generateBindMounts,
@@ -46,6 +47,8 @@ export const buildLibsql = async (libsql: LibsqlNested) => {
env ? `\n${env}` : ""
}${sqldNode === "replica" ? `\nSQLD_PRIMARY_URL="${sqldPrimaryUrl}"` : ""}`;
const resolvedNetworks = await resolveServiceNetworks(libsql);
const {
HealthCheck,
RestartPolicy,
@@ -54,7 +57,6 @@ export const buildLibsql = async (libsql: LibsqlNested) => {
Mode,
RollbackConfig,
UpdateConfig,
Networks,
} = generateConfigContainer(libsql);
const resources = calculateResources({
memoryLimit,
@@ -96,7 +98,7 @@ export const buildLibsql = async (libsql: LibsqlNested) => {
: {}),
Labels,
},
Networks,
Networks: resolvedNetworks,
RestartPolicy,
Placement,
Resources: {

View File

@@ -1,5 +1,6 @@
import type { InferResultType } from "@dokploy/server/types/with";
import type { CreateServiceOptions } from "dockerode";
import { resolveServiceNetworks } from "../../services/network";
import {
calculateResources,
generateBindMounts,
@@ -37,6 +38,8 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
env ? `\n${env}` : ""
}`;
const resolvedNetworks = await resolveServiceNetworks(mariadb);
const {
HealthCheck,
RestartPolicy,
@@ -45,7 +48,6 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
Mode,
RollbackConfig,
UpdateConfig,
Networks,
StopGracePeriod,
EndpointSpec,
Ulimits,
@@ -87,7 +89,7 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
...(Ulimits && { Ulimits }),
Labels,
},
Networks,
Networks: resolvedNetworks,
RestartPolicy,
Placement,
Resources: {

View File

@@ -1,5 +1,6 @@
import type { InferResultType } from "@dokploy/server/types/with";
import type { CreateServiceOptions } from "dockerode";
import { resolveServiceNetworks } from "../../services/network";
import {
calculateResources,
generateBindMounts,
@@ -83,6 +84,8 @@ ${command ?? "wait $MONGOD_PID"}`;
env ? `\n${env}` : ""
}`;
const resolvedNetworks = await resolveServiceNetworks(mongo);
const {
HealthCheck,
RestartPolicy,
@@ -91,7 +94,6 @@ ${command ?? "wait $MONGOD_PID"}`;
Mode,
RollbackConfig,
UpdateConfig,
Networks,
StopGracePeriod,
EndpointSpec,
Ulimits,
@@ -143,7 +145,7 @@ ${command ?? "wait $MONGOD_PID"}`;
...(Ulimits && { Ulimits }),
Labels,
},
Networks,
Networks: resolvedNetworks,
RestartPolicy,
Placement,
Resources: {

View File

@@ -1,5 +1,6 @@
import type { InferResultType } from "@dokploy/server/types/with";
import type { CreateServiceOptions } from "dockerode";
import { resolveServiceNetworks } from "../../services/network";
import {
calculateResources,
generateBindMounts,
@@ -43,6 +44,8 @@ export const buildMysql = async (mysql: MysqlNested) => {
env ? `\n${env}` : ""
}`;
const resolvedNetworks = await resolveServiceNetworks(mysql);
const {
HealthCheck,
RestartPolicy,
@@ -51,7 +54,6 @@ export const buildMysql = async (mysql: MysqlNested) => {
Mode,
RollbackConfig,
UpdateConfig,
Networks,
StopGracePeriod,
EndpointSpec,
Ulimits,
@@ -93,7 +95,7 @@ export const buildMysql = async (mysql: MysqlNested) => {
...(Ulimits && { Ulimits }),
Labels,
},
Networks,
Networks: resolvedNetworks,
RestartPolicy,
Placement,
Resources: {

View File

@@ -1,5 +1,6 @@
import type { InferResultType } from "@dokploy/server/types/with";
import type { CreateServiceOptions } from "dockerode";
import { resolveServiceNetworks } from "../../services/network";
import {
calculateResources,
generateBindMounts,
@@ -36,6 +37,8 @@ export const buildPostgres = async (postgres: PostgresNested) => {
env ? `\n${env}` : ""
}`;
const resolvedNetworks = await resolveServiceNetworks(postgres);
const {
HealthCheck,
RestartPolicy,
@@ -44,7 +47,6 @@ export const buildPostgres = async (postgres: PostgresNested) => {
Mode,
RollbackConfig,
UpdateConfig,
Networks,
StopGracePeriod,
EndpointSpec,
Ulimits,
@@ -85,7 +87,7 @@ export const buildPostgres = async (postgres: PostgresNested) => {
...(Ulimits && { Ulimits }),
Labels,
},
Networks,
Networks: resolvedNetworks,
RestartPolicy,
Placement,
Resources: {

View File

@@ -1,5 +1,6 @@
import type { InferResultType } from "@dokploy/server/types/with";
import type { CreateServiceOptions } from "dockerode";
import { resolveServiceNetworks } from "../../services/network";
import {
calculateResources,
generateBindMounts,
@@ -34,6 +35,8 @@ export const buildRedis = async (redis: RedisNested) => {
env ? `\n${env}` : ""
}`;
const resolvedNetworks = await resolveServiceNetworks(redis);
const {
HealthCheck,
RestartPolicy,
@@ -42,7 +45,6 @@ export const buildRedis = async (redis: RedisNested) => {
Mode,
RollbackConfig,
UpdateConfig,
Networks,
StopGracePeriod,
EndpointSpec,
Ulimits,
@@ -91,7 +93,7 @@ export const buildRedis = async (redis: RedisNested) => {
...(Ulimits && { Ulimits }),
Labels,
},
Networks,
Networks: resolvedNetworks,
RestartPolicy,
Placement,
Resources: {

View File

@@ -1,8 +1,11 @@
import fs, { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { paths } from "@dokploy/server/constants";
import { db } from "@dokploy/server/db";
import { network } from "@dokploy/server/db/schema";
import type { Compose } from "@dokploy/server/services/compose";
import type { Domain } from "@dokploy/server/services/domain";
import { inArray } from "drizzle-orm";
import { parse, stringify } from "yaml";
import { execAsyncRemote } from "../process/execAsync";
import { cloneBitbucketRepository } from "../providers/bitbucket";
@@ -228,14 +231,79 @@ export const addDomainToCompose = async (
}
}
// Add dokploy-network to the root of the compose file
const injectedNetworkNames = await applyServiceNetworks(result, compose);
if (!compose.isolatedDeployment) {
result.networks = addDokployNetworkToRoot(result.networks);
declareUsedNetworksInRoot(result, injectedNetworkNames);
}
return result;
};
const applyServiceNetworks = async (
result: ComposeSpecification,
compose: Compose,
) => {
const injectedNetworkNames = new Set<string>();
const serviceNetworks = compose.serviceNetworks ?? [];
if (serviceNetworks.length === 0) return injectedNetworkNames;
const allNetworkIds = [
...new Set(serviceNetworks.flatMap((s) => s.networkIds)),
];
const networks =
allNetworkIds.length > 0
? await db.query.network.findMany({
where: inArray(network.networkId, allNetworkIds),
})
: [];
for (const config of serviceNetworks) {
const service = result.services?.[config.serviceName];
if (!service) continue;
for (const networkId of config.networkIds) {
const match = networks.find((n) => n.networkId === networkId);
if (!match) continue;
service.networks = addDokployNetworkToService(
service.networks,
match.name,
);
injectedNetworkNames.add(match.name);
}
if (config.detachDokployNetwork) {
removeNetworkFromService(service, "dokploy-network");
removeNetworkFromService(service, "default");
removeDokployNetworkLabel(service);
}
}
return injectedNetworkNames;
};
const declareUsedNetworksInRoot = (
result: ComposeSpecification,
injectedNetworkNames: Set<string>,
) => {
const isUsed = (name: string) =>
Object.values(result.services ?? {}).some((service) => {
const nets = service?.networks;
if (Array.isArray(nets)) return nets.includes(name);
if (nets && typeof nets === "object") return name in nets;
return false;
});
if (isUsed("dokploy-network")) {
result.networks = addDokployNetworkToRoot(result.networks);
}
for (const name of injectedNetworkNames) {
if (isUsed(name)) {
result.networks = addDokployNetworkToRoot(result.networks, name);
}
}
};
export const writeComposeFile = async (
compose: Compose,
composeSpec: ComposeSpecification,
@@ -349,9 +417,10 @@ export const createDomainLabels = (
export const addDokployNetworkToService = (
networkService: DefinitionsService["networks"],
networkName = "dokploy-network",
) => {
let networks = networkService;
const network = "dokploy-network";
const network = networkName;
const defaultNetwork = "default";
if (!networks) {
networks = [];
@@ -376,11 +445,40 @@ export const addDokployNetworkToService = (
return networks;
};
export const removeNetworkFromService = (
service: DefinitionsService,
networkName: string,
) => {
const networks = service.networks;
if (Array.isArray(networks)) {
service.networks = networks.filter((n) => n !== networkName);
} else if (networks && typeof networks === "object") {
delete networks[networkName];
}
};
const removeDokployNetworkLabel = (service: DefinitionsService) => {
const stripped = (labels: DefinitionsService["labels"]) => {
if (Array.isArray(labels)) {
return labels.filter(
(l) =>
l !== "traefik.docker.network=dokploy-network" &&
l !== "traefik.swarm.network=dokploy-network",
);
}
return labels;
};
if (service.labels) service.labels = stripped(service.labels);
if (service.deploy?.labels)
service.deploy.labels = stripped(service.deploy.labels);
};
export const addDokployNetworkToRoot = (
networkRoot: PropertiesNetworks | undefined,
networkName = "dokploy-network",
) => {
let networks = networkRoot;
const network = "dokploy-network";
const network = networkName;
if (!networks) {
networks = {};

View File

@@ -548,7 +548,6 @@ export const generateConfigContainer = (
labelsSwarm,
replicas,
mounts,
networkSwarm,
stopGracePeriodSwarm,
endpointSpecSwarm,
ulimitsSwarm,
@@ -611,13 +610,6 @@ export const generateConfigContainer = (
stopGracePeriodSwarm !== undefined && {
StopGracePeriod: stopGracePeriodSwarm,
}),
...(networkSwarm
? {
Networks: networkSwarm,
}
: {
Networks: [{ Target: "dokploy-network" }],
}),
...(endpointSpecSwarm && {
EndpointSpec: {
...(endpointSpecSwarm.Mode && { Mode: endpointSpecSwarm.Mode }),

View File

@@ -9,8 +9,12 @@ export const initSchedules = async () => {
where: eq(schedules.enabled, true),
with: {
server: true,
application: true,
compose: true,
application: {
columns: { applicationId: true, appName: true, serverId: true },
},
compose: {
columns: { composeId: true, appName: true, serverId: true },
},
organization: true,
},
});

View File

@@ -13,8 +13,8 @@ export const initVolumeBackupsCronJobs = async () => {
const volumeBackupsResult = await db.query.volumeBackups.findMany({
where: eq(volumeBackups.enabled, true),
with: {
application: true,
compose: true,
application: { columns: { applicationId: true } },
compose: { columns: { composeId: true } },
},
});