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
+4 -2
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: {
@@ -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: {
@@ -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: {
+4 -2
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: {
+4 -2
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: {
@@ -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: {
+4 -2
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: {
+102 -4
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 = {};
@@ -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 }),
+6 -2
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,
},
});
@@ -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 } },
},
});