From 4bf97b7894dee12752268d6a5709d3394ebf4571 Mon Sep 17 00:00:00 2001 From: Antoine Date: Fri, 10 Oct 2025 14:15:54 +0200 Subject: [PATCH 01/51] feat(db): add network table for custom docker networks --- apps/dokploy/drizzle/0117_aspiring_logan.sql | 30 + apps/dokploy/drizzle/meta/0117_snapshot.json | 6826 ++++++++++++++++++ apps/dokploy/drizzle/meta/_journal.json | 7 + packages/server/src/db/schema/index.ts | 1 + packages/server/src/db/schema/network.ts | 162 + 5 files changed, 7026 insertions(+) create mode 100644 apps/dokploy/drizzle/0117_aspiring_logan.sql create mode 100644 apps/dokploy/drizzle/meta/0117_snapshot.json create mode 100644 packages/server/src/db/schema/network.ts diff --git a/apps/dokploy/drizzle/0117_aspiring_logan.sql b/apps/dokploy/drizzle/0117_aspiring_logan.sql new file mode 100644 index 0000000000..eb3a73cc73 --- /dev/null +++ b/apps/dokploy/drizzle/0117_aspiring_logan.sql @@ -0,0 +1,30 @@ +CREATE TYPE "public"."networkDriver" AS ENUM('bridge', 'overlay');--> statement-breakpoint +CREATE TABLE "network" ( + "networkId" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "description" text, + "networkName" text NOT NULL, + "driver" "networkDriver" DEFAULT 'bridge' NOT NULL, + "isDefault" boolean DEFAULT false NOT NULL, + "subnet" text, + "gateway" text, + "ipRange" text, + "attachable" boolean DEFAULT true NOT NULL, + "internal" boolean DEFAULT false NOT NULL, + "dockerNetworkId" text, + "createdAt" text NOT NULL, + "projectId" text, + "organizationId" text NOT NULL, + "serverId" text +); +--> statement-breakpoint +ALTER TABLE "application" ADD COLUMN "customNetworkIds" text[];--> statement-breakpoint +ALTER TABLE "compose" ADD COLUMN "customNetworkIds" text[];--> statement-breakpoint +ALTER TABLE "mariadb" ADD COLUMN "customNetworkIds" text[];--> statement-breakpoint +ALTER TABLE "mongo" ADD COLUMN "customNetworkIds" text[];--> statement-breakpoint +ALTER TABLE "mysql" ADD COLUMN "customNetworkIds" text[];--> statement-breakpoint +ALTER TABLE "postgres" ADD COLUMN "customNetworkIds" text[];--> statement-breakpoint +ALTER TABLE "redis" ADD COLUMN "customNetworkIds" text[];--> statement-breakpoint +ALTER TABLE "network" ADD CONSTRAINT "network_projectId_project_projectId_fk" FOREIGN KEY ("projectId") REFERENCES "public"."project"("projectId") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "network" ADD CONSTRAINT "network_organizationId_organization_id_fk" FOREIGN KEY ("organizationId") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "network" ADD CONSTRAINT "network_serverId_server_serverId_fk" FOREIGN KEY ("serverId") REFERENCES "public"."server"("serverId") ON DELETE set null ON UPDATE no action; diff --git a/apps/dokploy/drizzle/meta/0117_snapshot.json b/apps/dokploy/drizzle/meta/0117_snapshot.json new file mode 100644 index 0000000000..e4758b479a --- /dev/null +++ b/apps/dokploy/drizzle/meta/0117_snapshot.json @@ -0,0 +1,6826 @@ +{ + "id": "02653ff8-9cf4-4ad3-8712-b537f3b08483", + "prevId": "dd601427-c873-4923-b090-4754128cf284", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is2FAEnabled": { + "name": "is2FAEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "resetPasswordToken": { + "name": "resetPasswordToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resetPasswordExpiresAt": { + "name": "resetPasswordExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationToken": { + "name": "confirmationToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationExpiresAt": { + "name": "confirmationExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_temp_id_fk": { + "name": "account_user_id_user_temp_id_fk", + "tableFrom": "account", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.apikey": { + "name": "apikey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "apikey_user_id_user_temp_id_fk": { + "name": "apikey_user_id_user_temp_id_fk", + "tableFrom": "apikey", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_temp_id_fk": { + "name": "invitation_inviter_id_user_temp_id_fk", + "tableFrom": "invitation", + "tableTo": "user_temp", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canCreateProjects": { + "name": "canCreateProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToSSHKeys": { + "name": "canAccessToSSHKeys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canCreateServices": { + "name": "canCreateServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteProjects": { + "name": "canDeleteProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteServices": { + "name": "canDeleteServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToDocker": { + "name": "canAccessToDocker", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToAPI": { + "name": "canAccessToAPI", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToGitProviders": { + "name": "canAccessToGitProviders", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToTraefikFiles": { + "name": "canAccessToTraefikFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteEnvironments": { + "name": "canDeleteEnvironments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canCreateEnvironments": { + "name": "canCreateEnvironments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "accesedProjects": { + "name": "accesedProjects", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "accessedEnvironments": { + "name": "accessedEnvironments", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "accesedServices": { + "name": "accesedServices", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + } + }, + "indexes": {}, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_temp_id_fk": { + "name": "member_user_id_user_temp_id_fk", + "tableFrom": "member", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "organization_owner_id_user_temp_id_fk": { + "name": "organization_owner_id_user_temp_id_fk", + "tableFrom": "organization", + "tableTo": "user_temp", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_temp_id_fk": { + "name": "two_factor_user_id_user_temp_id_fk", + "tableFrom": "two_factor", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai": { + "name": "ai", + "schema": "", + "columns": { + "aiId": { + "name": "aiId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "apiUrl": { + "name": "apiUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "apiKey": { + "name": "apiKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "isEnabled": { + "name": "isEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ai_organizationId_organization_id_fk": { + "name": "ai_organizationId_organization_id_fk", + "tableFrom": "ai", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.application": { + "name": "application", + "schema": "", + "columns": { + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewEnv": { + "name": "previewEnv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "watchPaths": { + "name": "watchPaths", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "previewBuildArgs": { + "name": "previewBuildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewLabels": { + "name": "previewLabels", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "previewWildcard": { + "name": "previewWildcard", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewPort": { + "name": "previewPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "previewHttps": { + "name": "previewHttps", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "previewPath": { + "name": "previewPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "previewCustomCertResolver": { + "name": "previewCustomCertResolver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewLimit": { + "name": "previewLimit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "isPreviewDeploymentsActive": { + "name": "isPreviewDeploymentsActive", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "previewRequireCollaboratorPermissions": { + "name": "previewRequireCollaboratorPermissions", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rollbackActive": { + "name": "rollbackActive", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "buildArgs": { + "name": "buildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "cleanCache": { + "name": "cleanCache", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildPath": { + "name": "buildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "triggerType": { + "name": "triggerType", + "type": "triggerType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'push'" + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBuildPath": { + "name": "gitlabBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaRepository": { + "name": "giteaRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaOwner": { + "name": "giteaOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaBranch": { + "name": "giteaBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaBuildPath": { + "name": "giteaBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBuildPath": { + "name": "bitbucketBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBuildPath": { + "name": "customGitBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableSubmodules": { + "name": "enableSubmodules", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerfile": { + "name": "dockerfile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerContextPath": { + "name": "dockerContextPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerBuildStage": { + "name": "dockerBuildStage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dropBuildPath": { + "name": "dropBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "customNetworkIds": { + "name": "customNetworkIds", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "buildType": { + "name": "buildType", + "type": "buildType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'nixpacks'" + }, + "railpackVersion": { + "name": "railpackVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'0.2.2'" + }, + "herokuVersion": { + "name": "herokuVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'24'" + }, + "publishDirectory": { + "name": "publishDirectory", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isStaticSpa": { + "name": "isStaticSpa", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "application", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_registryId_registry_registryId_fk": { + "name": "application_registryId_registry_registryId_fk", + "tableFrom": "application", + "tableTo": "registry", + "columnsFrom": [ + "registryId" + ], + "columnsTo": [ + "registryId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_environmentId_environment_environmentId_fk": { + "name": "application_environmentId_environment_environmentId_fk", + "tableFrom": "application", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "application_githubId_github_githubId_fk": { + "name": "application_githubId_github_githubId_fk", + "tableFrom": "application", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_gitlabId_gitlab_gitlabId_fk": { + "name": "application_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "application", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_giteaId_gitea_giteaId_fk": { + "name": "application_giteaId_gitea_giteaId_fk", + "tableFrom": "application", + "tableTo": "gitea", + "columnsFrom": [ + "giteaId" + ], + "columnsTo": [ + "giteaId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "application_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "application", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_serverId_server_serverId_fk": { + "name": "application_serverId_server_serverId_fk", + "tableFrom": "application", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "application_appName_unique": { + "name": "application_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.backup": { + "name": "backup", + "schema": "", + "columns": { + "backupId": { + "name": "backupId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "database": { + "name": "database", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "keepLatestCount": { + "name": "keepLatestCount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "backupType": { + "name": "backupType", + "type": "backupType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'database'" + }, + "databaseType": { + "name": "databaseType", + "type": "databaseType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "backup_destinationId_destination_destinationId_fk": { + "name": "backup_destinationId_destination_destinationId_fk", + "tableFrom": "backup", + "tableTo": "destination", + "columnsFrom": [ + "destinationId" + ], + "columnsTo": [ + "destinationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_composeId_compose_composeId_fk": { + "name": "backup_composeId_compose_composeId_fk", + "tableFrom": "backup", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_postgresId_postgres_postgresId_fk": { + "name": "backup_postgresId_postgres_postgresId_fk", + "tableFrom": "backup", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mariadbId_mariadb_mariadbId_fk": { + "name": "backup_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "backup", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mysqlId_mysql_mysqlId_fk": { + "name": "backup_mysqlId_mysql_mysqlId_fk", + "tableFrom": "backup", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mongoId_mongo_mongoId_fk": { + "name": "backup_mongoId_mongo_mongoId_fk", + "tableFrom": "backup", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_userId_user_temp_id_fk": { + "name": "backup_userId_user_temp_id_fk", + "tableFrom": "backup", + "tableTo": "user_temp", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "backup_appName_unique": { + "name": "backup_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bitbucket": { + "name": "bitbucket", + "schema": "", + "columns": { + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "bitbucketUsername": { + "name": "bitbucketUsername", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketEmail": { + "name": "bitbucketEmail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "appPassword": { + "name": "appPassword", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "apiToken": { + "name": "apiToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketWorkspaceName": { + "name": "bitbucketWorkspaceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "bitbucket_gitProviderId_git_provider_gitProviderId_fk": { + "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "bitbucket", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.certificate": { + "name": "certificate", + "schema": "", + "columns": { + "certificateId": { + "name": "certificateId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificateData": { + "name": "certificateData", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificatePath": { + "name": "certificatePath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autoRenew": { + "name": "autoRenew", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "certificate_organizationId_organization_id_fk": { + "name": "certificate_organizationId_organization_id_fk", + "tableFrom": "certificate", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "certificate_serverId_server_serverId_fk": { + "name": "certificate_serverId_server_serverId_fk", + "tableFrom": "certificate", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "certificate_certificatePath_unique": { + "name": "certificate_certificatePath_unique", + "nullsNotDistinct": false, + "columns": [ + "certificatePath" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.compose": { + "name": "compose", + "schema": "", + "columns": { + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeFile": { + "name": "composeFile", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceTypeCompose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "composeType": { + "name": "composeType", + "type": "composeType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'docker-compose'" + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaRepository": { + "name": "giteaRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaOwner": { + "name": "giteaOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaBranch": { + "name": "giteaBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "enableSubmodules": { + "name": "enableSubmodules", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "composePath": { + "name": "composePath", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'./docker-compose.yml'" + }, + "suffix": { + "name": "suffix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "randomize": { + "name": "randomize", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "isolatedDeployment": { + "name": "isolatedDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "isolatedDeploymentsVolume": { + "name": "isolatedDeploymentsVolume", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "triggerType": { + "name": "triggerType", + "type": "triggerType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'push'" + }, + "composeStatus": { + "name": "composeStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "customNetworkIds": { + "name": "customNetworkIds", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "watchPaths": { + "name": "watchPaths", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "compose", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_environmentId_environment_environmentId_fk": { + "name": "compose_environmentId_environment_environmentId_fk", + "tableFrom": "compose", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "compose_githubId_github_githubId_fk": { + "name": "compose_githubId_github_githubId_fk", + "tableFrom": "compose", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_gitlabId_gitlab_gitlabId_fk": { + "name": "compose_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "compose", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "compose_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "compose", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_giteaId_gitea_giteaId_fk": { + "name": "compose_giteaId_gitea_giteaId_fk", + "tableFrom": "compose", + "tableTo": "gitea", + "columnsFrom": [ + "giteaId" + ], + "columnsTo": [ + "giteaId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_serverId_server_serverId_fk": { + "name": "compose_serverId_server_serverId_fk", + "tableFrom": "compose", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment": { + "name": "deployment", + "schema": "", + "columns": { + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "deploymentStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'running'" + }, + "logPath": { + "name": "logPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pid": { + "name": "pid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPreviewDeployment": { + "name": "isPreviewDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "startedAt": { + "name": "startedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finishedAt": { + "name": "finishedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "errorMessage": { + "name": "errorMessage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scheduleId": { + "name": "scheduleId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backupId": { + "name": "backupId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rollbackId": { + "name": "rollbackId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volumeBackupId": { + "name": "volumeBackupId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "deployment_applicationId_application_applicationId_fk": { + "name": "deployment_applicationId_application_applicationId_fk", + "tableFrom": "deployment", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_composeId_compose_composeId_fk": { + "name": "deployment_composeId_compose_composeId_fk", + "tableFrom": "deployment", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_serverId_server_serverId_fk": { + "name": "deployment_serverId_server_serverId_fk", + "tableFrom": "deployment", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "deployment", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_scheduleId_schedule_scheduleId_fk": { + "name": "deployment_scheduleId_schedule_scheduleId_fk", + "tableFrom": "deployment", + "tableTo": "schedule", + "columnsFrom": [ + "scheduleId" + ], + "columnsTo": [ + "scheduleId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_backupId_backup_backupId_fk": { + "name": "deployment_backupId_backup_backupId_fk", + "tableFrom": "deployment", + "tableTo": "backup", + "columnsFrom": [ + "backupId" + ], + "columnsTo": [ + "backupId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_rollbackId_rollback_rollbackId_fk": { + "name": "deployment_rollbackId_rollback_rollbackId_fk", + "tableFrom": "deployment", + "tableTo": "rollback", + "columnsFrom": [ + "rollbackId" + ], + "columnsTo": [ + "rollbackId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_volumeBackupId_volume_backup_volumeBackupId_fk": { + "name": "deployment_volumeBackupId_volume_backup_volumeBackupId_fk", + "tableFrom": "deployment", + "tableTo": "volume_backup", + "columnsFrom": [ + "volumeBackupId" + ], + "columnsTo": [ + "volumeBackupId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.destination": { + "name": "destination", + "schema": "", + "columns": { + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessKey": { + "name": "accessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secretAccessKey": { + "name": "secretAccessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "destination_organizationId_organization_id_fk": { + "name": "destination_organizationId_organization_id_fk", + "tableFrom": "destination", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.domain": { + "name": "domain", + "schema": "", + "columns": { + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domainType": { + "name": "domainType", + "type": "domainType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'application'" + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customCertResolver": { + "name": "customCertResolver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "internalPath": { + "name": "internalPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "stripPath": { + "name": "stripPath", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "domain_composeId_compose_composeId_fk": { + "name": "domain_composeId_compose_composeId_fk", + "tableFrom": "domain", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_applicationId_application_applicationId_fk": { + "name": "domain_applicationId_application_applicationId_fk", + "tableFrom": "domain", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "domain", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "environment_projectId_project_projectId_fk": { + "name": "environment_projectId_project_projectId_fk", + "tableFrom": "environment", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.git_provider": { + "name": "git_provider", + "schema": "", + "columns": { + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerType": { + "name": "providerType", + "type": "gitProviderType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "git_provider_organizationId_organization_id_fk": { + "name": "git_provider_organizationId_organization_id_fk", + "tableFrom": "git_provider", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "git_provider_userId_user_temp_id_fk": { + "name": "git_provider_userId_user_temp_id_fk", + "tableFrom": "git_provider", + "tableTo": "user_temp", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gitea": { + "name": "gitea", + "schema": "", + "columns": { + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "giteaUrl": { + "name": "giteaUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'https://gitea.com'" + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'repo,repo:status,read:user,read:org'" + }, + "last_authenticated_at": { + "name": "last_authenticated_at", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "gitea_gitProviderId_git_provider_gitProviderId_fk": { + "name": "gitea_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "gitea", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github": { + "name": "github", + "schema": "", + "columns": { + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "githubAppName": { + "name": "githubAppName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubAppId": { + "name": "githubAppId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "githubClientId": { + "name": "githubClientId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubClientSecret": { + "name": "githubClientSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubInstallationId": { + "name": "githubInstallationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubPrivateKey": { + "name": "githubPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubWebhookSecret": { + "name": "githubWebhookSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "github_gitProviderId_git_provider_gitProviderId_fk": { + "name": "github_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "github", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gitlab": { + "name": "gitlab", + "schema": "", + "columns": { + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "gitlabUrl": { + "name": "gitlabUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'https://gitlab.com'" + }, + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "gitlab_gitProviderId_git_provider_gitProviderId_fk": { + "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "gitlab", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mariadb": { + "name": "mariadb", + "schema": "", + "columns": { + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "customNetworkIds": { + "name": "customNetworkIds", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mariadb_environmentId_environment_environmentId_fk": { + "name": "mariadb_environmentId_environment_environmentId_fk", + "tableFrom": "mariadb", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mariadb_serverId_server_serverId_fk": { + "name": "mariadb_serverId_server_serverId_fk", + "tableFrom": "mariadb", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mariadb_appName_unique": { + "name": "mariadb_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mongo": { + "name": "mongo", + "schema": "", + "columns": { + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "customNetworkIds": { + "name": "customNetworkIds", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replicaSets": { + "name": "replicaSets", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "mongo_environmentId_environment_environmentId_fk": { + "name": "mongo_environmentId_environment_environmentId_fk", + "tableFrom": "mongo", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mongo_serverId_server_serverId_fk": { + "name": "mongo_serverId_server_serverId_fk", + "tableFrom": "mongo", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mongo_appName_unique": { + "name": "mongo_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mount": { + "name": "mount", + "schema": "", + "columns": { + "mountId": { + "name": "mountId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "mountType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "hostPath": { + "name": "hostPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volumeName": { + "name": "volumeName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filePath": { + "name": "filePath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serviceType": { + "name": "serviceType", + "type": "serviceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "mountPath": { + "name": "mountPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mount_applicationId_application_applicationId_fk": { + "name": "mount_applicationId_application_applicationId_fk", + "tableFrom": "mount", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_postgresId_postgres_postgresId_fk": { + "name": "mount_postgresId_postgres_postgresId_fk", + "tableFrom": "mount", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mariadbId_mariadb_mariadbId_fk": { + "name": "mount_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "mount", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mongoId_mongo_mongoId_fk": { + "name": "mount_mongoId_mongo_mongoId_fk", + "tableFrom": "mount", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mysqlId_mysql_mysqlId_fk": { + "name": "mount_mysqlId_mysql_mysqlId_fk", + "tableFrom": "mount", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_redisId_redis_redisId_fk": { + "name": "mount_redisId_redis_redisId_fk", + "tableFrom": "mount", + "tableTo": "redis", + "columnsFrom": [ + "redisId" + ], + "columnsTo": [ + "redisId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_composeId_compose_composeId_fk": { + "name": "mount_composeId_compose_composeId_fk", + "tableFrom": "mount", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mysql": { + "name": "mysql", + "schema": "", + "columns": { + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "customNetworkIds": { + "name": "customNetworkIds", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mysql_environmentId_environment_environmentId_fk": { + "name": "mysql_environmentId_environment_environmentId_fk", + "tableFrom": "mysql", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mysql_serverId_server_serverId_fk": { + "name": "mysql_serverId_server_serverId_fk", + "tableFrom": "mysql", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mysql_appName_unique": { + "name": "mysql_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.network": { + "name": "network", + "schema": "", + "columns": { + "networkId": { + "name": "networkId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "networkName": { + "name": "networkName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "driver": { + "name": "driver", + "type": "networkDriver", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'bridge'" + }, + "isDefault": { + "name": "isDefault", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "subnet": { + "name": "subnet", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gateway": { + "name": "gateway", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ipRange": { + "name": "ipRange", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attachable": { + "name": "attachable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "internal": { + "name": "internal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerNetworkId": { + "name": "dockerNetworkId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "network_projectId_project_projectId_fk": { + "name": "network_projectId_project_projectId_fk", + "tableFrom": "network", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "network_organizationId_organization_id_fk": { + "name": "network_organizationId_organization_id_fk", + "tableFrom": "network", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "network_serverId_server_serverId_fk": { + "name": "network_serverId_server_serverId_fk", + "tableFrom": "network", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.discord": { + "name": "discord", + "schema": "", + "columns": { + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decoration": { + "name": "decoration", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email": { + "name": "email", + "schema": "", + "columns": { + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "smtpServer": { + "name": "smtpServer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "smtpPort": { + "name": "smtpPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fromAddress": { + "name": "fromAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "toAddress": { + "name": "toAddress", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gotify": { + "name": "gotify", + "schema": "", + "columns": { + "gotifyId": { + "name": "gotifyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serverUrl": { + "name": "serverUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appToken": { + "name": "appToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "decoration": { + "name": "decoration", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "notificationId": { + "name": "notificationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appDeploy": { + "name": "appDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "appBuildError": { + "name": "appBuildError", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "databaseBackup": { + "name": "databaseBackup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dokployRestart": { + "name": "dokployRestart", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerCleanup": { + "name": "dockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "serverThreshold": { + "name": "serverThreshold", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notificationType": { + "name": "notificationType", + "type": "notificationType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gotifyId": { + "name": "gotifyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ntfyId": { + "name": "ntfyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "notification_slackId_slack_slackId_fk": { + "name": "notification_slackId_slack_slackId_fk", + "tableFrom": "notification", + "tableTo": "slack", + "columnsFrom": [ + "slackId" + ], + "columnsTo": [ + "slackId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_telegramId_telegram_telegramId_fk": { + "name": "notification_telegramId_telegram_telegramId_fk", + "tableFrom": "notification", + "tableTo": "telegram", + "columnsFrom": [ + "telegramId" + ], + "columnsTo": [ + "telegramId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_discordId_discord_discordId_fk": { + "name": "notification_discordId_discord_discordId_fk", + "tableFrom": "notification", + "tableTo": "discord", + "columnsFrom": [ + "discordId" + ], + "columnsTo": [ + "discordId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_emailId_email_emailId_fk": { + "name": "notification_emailId_email_emailId_fk", + "tableFrom": "notification", + "tableTo": "email", + "columnsFrom": [ + "emailId" + ], + "columnsTo": [ + "emailId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_gotifyId_gotify_gotifyId_fk": { + "name": "notification_gotifyId_gotify_gotifyId_fk", + "tableFrom": "notification", + "tableTo": "gotify", + "columnsFrom": [ + "gotifyId" + ], + "columnsTo": [ + "gotifyId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_ntfyId_ntfy_ntfyId_fk": { + "name": "notification_ntfyId_ntfy_ntfyId_fk", + "tableFrom": "notification", + "tableTo": "ntfy", + "columnsFrom": [ + "ntfyId" + ], + "columnsTo": [ + "ntfyId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_organizationId_organization_id_fk": { + "name": "notification_organizationId_organization_id_fk", + "tableFrom": "notification", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ntfy": { + "name": "ntfy", + "schema": "", + "columns": { + "ntfyId": { + "name": "ntfyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serverUrl": { + "name": "serverUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "topic": { + "name": "topic", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accessToken": { + "name": "accessToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack": { + "name": "slack", + "schema": "", + "columns": { + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.telegram": { + "name": "telegram", + "schema": "", + "columns": { + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "botToken": { + "name": "botToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chatId": { + "name": "chatId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "messageThreadId": { + "name": "messageThreadId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.port": { + "name": "port", + "schema": "", + "columns": { + "portId": { + "name": "portId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "publishedPort": { + "name": "publishedPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "publishMode": { + "name": "publishMode", + "type": "publishModeType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'host'" + }, + "targetPort": { + "name": "targetPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "protocolType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "port_applicationId_application_applicationId_fk": { + "name": "port_applicationId_application_applicationId_fk", + "tableFrom": "port", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.postgres": { + "name": "postgres", + "schema": "", + "columns": { + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "customNetworkIds": { + "name": "customNetworkIds", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "postgres_environmentId_environment_environmentId_fk": { + "name": "postgres_environmentId_environment_environmentId_fk", + "tableFrom": "postgres", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "postgres_serverId_server_serverId_fk": { + "name": "postgres_serverId_server_serverId_fk", + "tableFrom": "postgres", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "postgres_appName_unique": { + "name": "postgres_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.preview_deployments": { + "name": "preview_deployments", + "schema": "", + "columns": { + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestId": { + "name": "pullRequestId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestNumber": { + "name": "pullRequestNumber", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestURL": { + "name": "pullRequestURL", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestTitle": { + "name": "pullRequestTitle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestCommentId": { + "name": "pullRequestCommentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previewStatus": { + "name": "previewStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "preview_deployments_applicationId_application_applicationId_fk": { + "name": "preview_deployments_applicationId_application_applicationId_fk", + "tableFrom": "preview_deployments", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "preview_deployments_domainId_domain_domainId_fk": { + "name": "preview_deployments_domainId_domain_domainId_fk", + "tableFrom": "preview_deployments", + "tableTo": "domain", + "columnsFrom": [ + "domainId" + ], + "columnsTo": [ + "domainId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "preview_deployments_appName_unique": { + "name": "preview_deployments_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "project_organizationId_organization_id_fk": { + "name": "project_organizationId_organization_id_fk", + "tableFrom": "project", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.redirect": { + "name": "redirect", + "schema": "", + "columns": { + "redirectId": { + "name": "redirectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "regex": { + "name": "regex", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permanent": { + "name": "permanent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "redirect_applicationId_application_applicationId_fk": { + "name": "redirect_applicationId_application_applicationId_fk", + "tableFrom": "redirect", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.redis": { + "name": "redis", + "schema": "", + "columns": { + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "customNetworkIds": { + "name": "customNetworkIds", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "redis_environmentId_environment_environmentId_fk": { + "name": "redis_environmentId_environment_environmentId_fk", + "tableFrom": "redis", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "redis_serverId_server_serverId_fk": { + "name": "redis_serverId_server_serverId_fk", + "tableFrom": "redis", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "redis_appName_unique": { + "name": "redis_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.registry": { + "name": "registry", + "schema": "", + "columns": { + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "registryName": { + "name": "registryName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imagePrefix": { + "name": "imagePrefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "selfHosted": { + "name": "selfHosted", + "type": "RegistryType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'cloud'" + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "registry_organizationId_organization_id_fk": { + "name": "registry_organizationId_organization_id_fk", + "tableFrom": "registry", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rollback": { + "name": "rollback", + "schema": "", + "columns": { + "rollbackId": { + "name": "rollbackId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fullContext": { + "name": "fullContext", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "rollback_deploymentId_deployment_deploymentId_fk": { + "name": "rollback_deploymentId_deployment_deploymentId_fk", + "tableFrom": "rollback", + "tableTo": "deployment", + "columnsFrom": [ + "deploymentId" + ], + "columnsTo": [ + "deploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schedule": { + "name": "schedule", + "schema": "", + "columns": { + "scheduleId": { + "name": "scheduleId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cronExpression": { + "name": "cronExpression", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shellType": { + "name": "shellType", + "type": "shellType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'bash'" + }, + "scheduleType": { + "name": "scheduleType", + "type": "scheduleType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "script": { + "name": "script", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "schedule_applicationId_application_applicationId_fk": { + "name": "schedule_applicationId_application_applicationId_fk", + "tableFrom": "schedule", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_composeId_compose_composeId_fk": { + "name": "schedule_composeId_compose_composeId_fk", + "tableFrom": "schedule", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_serverId_server_serverId_fk": { + "name": "schedule_serverId_server_serverId_fk", + "tableFrom": "schedule", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_userId_user_temp_id_fk": { + "name": "schedule_userId_user_temp_id_fk", + "tableFrom": "schedule", + "tableTo": "user_temp", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security": { + "name": "security", + "schema": "", + "columns": { + "securityId": { + "name": "securityId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "security_applicationId_application_applicationId_fk": { + "name": "security_applicationId_application_applicationId_fk", + "tableFrom": "security", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_username_applicationId_unique": { + "name": "security_username_applicationId_unique", + "nullsNotDistinct": false, + "columns": [ + "username", + "applicationId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.server": { + "name": "server", + "schema": "", + "columns": { + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'root'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverStatus": { + "name": "serverStatus", + "type": "serverStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metricsConfig": { + "name": "metricsConfig", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"server\":{\"type\":\"Remote\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"urlCallback\":\"\",\"cronJob\":\"\",\"retentionDays\":2,\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "server_organizationId_organization_id_fk": { + "name": "server_organizationId_organization_id_fk", + "tableFrom": "server", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "server_sshKeyId_ssh-key_sshKeyId_fk": { + "name": "server_sshKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "server", + "tableTo": "ssh-key", + "columnsFrom": [ + "sshKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_temp": { + "name": "session_temp", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_temp_user_id_user_temp_id_fk": { + "name": "session_temp_user_id_user_temp_id_fk", + "tableFrom": "session_temp", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_temp_token_unique": { + "name": "session_temp_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh-key": { + "name": "ssh-key", + "schema": "", + "columns": { + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastUsedAt": { + "name": "lastUsedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ssh-key_organizationId_organization_id_fk": { + "name": "ssh-key_organizationId_organization_id_fk", + "tableFrom": "ssh-key", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_temp": { + "name": "user_temp", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "isRegistered": { + "name": "isRegistered", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "expirationDate": { + "name": "expirationDate", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "serverIp": { + "name": "serverIp", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "letsEncryptEmail": { + "name": "letsEncryptEmail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sshPrivateKey": { + "name": "sshPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "logCleanupCron": { + "name": "logCleanupCron", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'0 0 * * *'" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "enablePaidFeatures": { + "name": "enablePaidFeatures", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allowImpersonation": { + "name": "allowImpersonation", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "metricsConfig": { + "name": "metricsConfig", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"server\":{\"type\":\"Dokploy\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"retentionDays\":2,\"cronJob\":\"\",\"urlCallback\":\"\",\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb" + }, + "cleanupCacheApplications": { + "name": "cleanupCacheApplications", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cleanupCacheOnPreviews": { + "name": "cleanupCacheOnPreviews", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cleanupCacheOnCompose": { + "name": "cleanupCacheOnCompose", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripeSubscriptionId": { + "name": "stripeSubscriptionId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serversQuantity": { + "name": "serversQuantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_temp_email_unique": { + "name": "user_temp_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volume_backup": { + "name": "volume_backup", + "schema": "", + "columns": { + "volumeBackupId": { + "name": "volumeBackupId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "volumeName": { + "name": "volumeName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceType": { + "name": "serviceType", + "type": "serviceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "turnOff": { + "name": "turnOff", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cronExpression": { + "name": "cronExpression", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "keepLatestCount": { + "name": "keepLatestCount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "volume_backup_applicationId_application_applicationId_fk": { + "name": "volume_backup_applicationId_application_applicationId_fk", + "tableFrom": "volume_backup", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_postgresId_postgres_postgresId_fk": { + "name": "volume_backup_postgresId_postgres_postgresId_fk", + "tableFrom": "volume_backup", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_mariadbId_mariadb_mariadbId_fk": { + "name": "volume_backup_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "volume_backup", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_mongoId_mongo_mongoId_fk": { + "name": "volume_backup_mongoId_mongo_mongoId_fk", + "tableFrom": "volume_backup", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_mysqlId_mysql_mysqlId_fk": { + "name": "volume_backup_mysqlId_mysql_mysqlId_fk", + "tableFrom": "volume_backup", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_redisId_redis_redisId_fk": { + "name": "volume_backup_redisId_redis_redisId_fk", + "tableFrom": "volume_backup", + "tableTo": "redis", + "columnsFrom": [ + "redisId" + ], + "columnsTo": [ + "redisId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_composeId_compose_composeId_fk": { + "name": "volume_backup_composeId_compose_composeId_fk", + "tableFrom": "volume_backup", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_destinationId_destination_destinationId_fk": { + "name": "volume_backup_destinationId_destination_destinationId_fk", + "tableFrom": "volume_backup", + "tableTo": "destination", + "columnsFrom": [ + "destinationId" + ], + "columnsTo": [ + "destinationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.buildType": { + "name": "buildType", + "schema": "public", + "values": [ + "dockerfile", + "heroku_buildpacks", + "paketo_buildpacks", + "nixpacks", + "static", + "railpack" + ] + }, + "public.sourceType": { + "name": "sourceType", + "schema": "public", + "values": [ + "docker", + "git", + "github", + "gitlab", + "bitbucket", + "gitea", + "drop" + ] + }, + "public.backupType": { + "name": "backupType", + "schema": "public", + "values": [ + "database", + "compose" + ] + }, + "public.databaseType": { + "name": "databaseType", + "schema": "public", + "values": [ + "postgres", + "mariadb", + "mysql", + "mongo", + "web-server" + ] + }, + "public.composeType": { + "name": "composeType", + "schema": "public", + "values": [ + "docker-compose", + "stack" + ] + }, + "public.sourceTypeCompose": { + "name": "sourceTypeCompose", + "schema": "public", + "values": [ + "git", + "github", + "gitlab", + "bitbucket", + "gitea", + "raw" + ] + }, + "public.deploymentStatus": { + "name": "deploymentStatus", + "schema": "public", + "values": [ + "running", + "done", + "error", + "cancelled" + ] + }, + "public.domainType": { + "name": "domainType", + "schema": "public", + "values": [ + "compose", + "application", + "preview" + ] + }, + "public.gitProviderType": { + "name": "gitProviderType", + "schema": "public", + "values": [ + "github", + "gitlab", + "bitbucket", + "gitea" + ] + }, + "public.mountType": { + "name": "mountType", + "schema": "public", + "values": [ + "bind", + "volume", + "file" + ] + }, + "public.serviceType": { + "name": "serviceType", + "schema": "public", + "values": [ + "application", + "postgres", + "mysql", + "mariadb", + "mongo", + "redis", + "compose" + ] + }, + "public.networkDriver": { + "name": "networkDriver", + "schema": "public", + "values": [ + "bridge", + "overlay" + ] + }, + "public.notificationType": { + "name": "notificationType", + "schema": "public", + "values": [ + "slack", + "telegram", + "discord", + "email", + "gotify", + "ntfy" + ] + }, + "public.protocolType": { + "name": "protocolType", + "schema": "public", + "values": [ + "tcp", + "udp" + ] + }, + "public.publishModeType": { + "name": "publishModeType", + "schema": "public", + "values": [ + "ingress", + "host" + ] + }, + "public.RegistryType": { + "name": "RegistryType", + "schema": "public", + "values": [ + "selfHosted", + "cloud" + ] + }, + "public.scheduleType": { + "name": "scheduleType", + "schema": "public", + "values": [ + "application", + "compose", + "server", + "dokploy-server" + ] + }, + "public.shellType": { + "name": "shellType", + "schema": "public", + "values": [ + "bash", + "sh" + ] + }, + "public.serverStatus": { + "name": "serverStatus", + "schema": "public", + "values": [ + "active", + "inactive" + ] + }, + "public.applicationStatus": { + "name": "applicationStatus", + "schema": "public", + "values": [ + "idle", + "running", + "done", + "error" + ] + }, + "public.certificateType": { + "name": "certificateType", + "schema": "public", + "values": [ + "letsencrypt", + "none", + "custom" + ] + }, + "public.triggerType": { + "name": "triggerType", + "schema": "public", + "values": [ + "push", + "tag" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/_journal.json b/apps/dokploy/drizzle/meta/_journal.json index 8b0bee6f75..dcbbacdd74 100644 --- a/apps/dokploy/drizzle/meta/_journal.json +++ b/apps/dokploy/drizzle/meta/_journal.json @@ -820,6 +820,13 @@ "when": 1759645163834, "tag": "0116_amusing_firedrake", "breakpoints": true + }, + { + "idx": 117, + "version": "7", + "when": 1760023259308, + "tag": "0117_aspiring_logan", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/server/src/db/schema/index.ts b/packages/server/src/db/schema/index.ts index c16ef1452f..0cf8b95cd8 100644 --- a/packages/server/src/db/schema/index.ts +++ b/packages/server/src/db/schema/index.ts @@ -17,6 +17,7 @@ export * from "./mariadb"; export * from "./mongo"; export * from "./mount"; export * from "./mysql"; +export * from "./network"; export * from "./notification"; export * from "./port"; export * from "./postgres"; diff --git a/packages/server/src/db/schema/network.ts b/packages/server/src/db/schema/network.ts new file mode 100644 index 0000000000..96986a0269 --- /dev/null +++ b/packages/server/src/db/schema/network.ts @@ -0,0 +1,162 @@ +import { relations } from "drizzle-orm"; +import { boolean, pgEnum, pgTable, text } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; +import { nanoid } from "nanoid"; +import { z } from "zod"; +import { organization } from "./account"; +import { applications } from "./application"; +import { compose } from "./compose"; +import { mariadb } from "./mariadb"; +import { mongo } from "./mongo"; +import { mysql } from "./mysql"; +import { postgres } from "./postgres"; +import { projects } from "./project"; +import { redis } from "./redis"; +import { server } from "./server"; + +export const networkDriver = pgEnum("networkDriver", ["bridge", "overlay"]); +export type NetworkDriver = (typeof networkDriver.enumValues)[number]; + +const IPV4_REGEX = + /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; +const CIDR_REGEX = + /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(?:[0-9]|[1-2][0-9]|3[0-2])$/; +const NETWORK_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/; + +export const networks = pgTable("network", { + networkId: text("networkId") + .notNull() + .primaryKey() + .$defaultFn(() => nanoid()), + name: text("name").notNull(), + description: text("description"), + networkName: text("networkName").notNull(), + driver: networkDriver("driver").notNull().default("bridge"), + isDefault: boolean("isDefault").notNull().default(false), + subnet: text("subnet"), + gateway: text("gateway"), + ipRange: text("ipRange"), + attachable: boolean("attachable").notNull().default(true), + internal: boolean("internal").notNull().default(false), + dockerNetworkId: text("dockerNetworkId"), + createdAt: text("createdAt") + .notNull() + .$defaultFn(() => new Date().toISOString()), + projectId: text("projectId").references(() => projects.projectId, { + onDelete: "set null", + }), + organizationId: text("organizationId") + .notNull() + .references(() => organization.id, { onDelete: "cascade" }), + serverId: text("serverId").references(() => server.serverId, { + onDelete: "set null", + }), +}); + +export const networkRelations = relations(networks, ({ one, many }) => ({ + project: one(projects, { + fields: [networks.projectId], + references: [projects.projectId], + }), + organization: one(organization, { + fields: [networks.organizationId], + references: [organization.id], + }), + server: one(server, { + fields: [networks.serverId], + references: [server.serverId], + }), + applications: many(applications), + composes: many(compose), + postgres: many(postgres), + mysql: many(mysql), + mariadb: many(mariadb), + mongo: many(mongo), + redis: many(redis), +})); + +const createSchema = createInsertSchema(networks, { + networkId: z.string().min(1), + name: z.string().min(1).max(50), + networkName: z + .string() + .min(1) + .max(63) + .regex( + NETWORK_NAME_REGEX, + "Network name must start with alphanumeric and contain only alphanumeric, underscore, period, or hyphen", + ), + subnet: z + .string() + .regex(CIDR_REGEX, "Invalid subnet format (e.g., 172.20.0.0/16)") + .optional(), + gateway: z + .string() + .regex(IPV4_REGEX, "Invalid gateway IP address") + .optional(), + ipRange: z.string().regex(CIDR_REGEX, "Invalid IP range format").optional(), +}); + +export const apiCreateNetwork = createSchema + .pick({ + name: true, + description: true, + networkName: true, + driver: true, + isDefault: true, + subnet: true, + gateway: true, + ipRange: true, + attachable: true, + internal: true, + projectId: true, + organizationId: true, + serverId: true, + }) + .extend({ + projectId: z.string().optional(), + organizationId: z.string().min(1).optional(), + }); + +export const apiFindOneNetwork = createSchema + .pick({ + networkId: true, + }) + .required(); + +export const apiRemoveNetwork = createSchema + .pick({ + networkId: true, + }) + .required(); + +export const apiUpdateNetwork = createSchema + .partial() + .extend({ + networkId: z.string().min(1), + }) + .omit({ dockerNetworkId: true, createdAt: true }); + +const RESOURCE_TYPES = [ + "application", + "compose", + "postgres", + "mysql", + "mariadb", + "mongo", + "redis", +] as const; + +export type NetworkResourceType = (typeof RESOURCE_TYPES)[number]; + +const networkResourceSchema = z.object({ + networkId: z.string().min(1), + resourceId: z.string().min(1), + resourceType: z.enum(RESOURCE_TYPES), +}); + +export const apiAssignNetworkToResource = networkResourceSchema; +export const apiRemoveNetworkFromResource = networkResourceSchema; + +export type Network = typeof networks.$inferSelect; +export type NetworkInsert = typeof networks.$inferInsert; From afb39a641b519d1b7c14e2bf7cb92cf0123481a6 Mon Sep 17 00:00:00 2001 From: Antoine Date: Fri, 10 Oct 2025 14:19:57 +0200 Subject: [PATCH 02/51] feat(db): add customNetworkIds field to all resources --- packages/server/src/db/schema/application.ts | 2 ++ packages/server/src/db/schema/compose.ts | 3 ++- packages/server/src/db/schema/mariadb.ts | 2 ++ packages/server/src/db/schema/mongo.ts | 2 ++ packages/server/src/db/schema/mysql.ts | 2 ++ packages/server/src/db/schema/postgres.ts | 2 ++ packages/server/src/db/schema/redis.ts | 3 ++- 7 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/server/src/db/schema/application.ts b/packages/server/src/db/schema/application.ts index 5310fe54ad..e21600bc1a 100644 --- a/packages/server/src/db/schema/application.ts +++ b/packages/server/src/db/schema/application.ts @@ -165,6 +165,7 @@ export const applications = pgTable("application", { labelsSwarm: json("labelsSwarm").$type(), networkSwarm: json("networkSwarm").$type(), stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }), + customNetworkIds: text("customNetworkIds").array(), // replicas: integer("replicas").default(1).notNull(), applicationStatus: applicationStatus("applicationStatus") @@ -314,6 +315,7 @@ const createSchema = createInsertSchema(applications, { previewLabels: z.array(z.string()).optional(), cleanCache: z.boolean().optional(), stopGracePeriodSwarm: z.bigint().nullable(), + customNetworkIds: z.array(z.string()).nullable(), }); export const apiCreateApplication = createSchema.pick({ diff --git a/packages/server/src/db/schema/compose.ts b/packages/server/src/db/schema/compose.ts index 958c2c32cd..59743f502e 100644 --- a/packages/server/src/db/schema/compose.ts +++ b/packages/server/src/db/schema/compose.ts @@ -12,7 +12,6 @@ import { gitea } from "./gitea"; import { github } from "./github"; import { gitlab } from "./gitlab"; import { mounts } from "./mount"; -import { projects } from "./project"; import { schedules } from "./schedule"; import { server } from "./server"; import { applicationStatus, triggerType } from "./shared"; @@ -85,6 +84,7 @@ export const compose = pgTable("compose", { .default(false), triggerType: triggerType("triggerType").default("push"), composeStatus: applicationStatus("composeStatus").notNull().default("idle"), + customNetworkIds: text("customNetworkIds").array(), environmentId: text("environmentId") .notNull() .references(() => environments.environmentId, { onDelete: "cascade" }), @@ -156,6 +156,7 @@ const createSchema = createInsertSchema(compose, { composePath: z.string().min(1), composeType: z.enum(["docker-compose", "stack"]).optional(), watchPaths: z.array(z.string()).optional(), + customNetworkIds: z.array(z.string()).nullable(), }); export const apiCreateCompose = createSchema.pick({ diff --git a/packages/server/src/db/schema/mariadb.ts b/packages/server/src/db/schema/mariadb.ts index 5dc32f24b1..f3a462a89e 100644 --- a/packages/server/src/db/schema/mariadb.ts +++ b/packages/server/src/db/schema/mariadb.ts @@ -63,6 +63,7 @@ export const mariadb = pgTable("mariadb", { labelsSwarm: json("labelsSwarm").$type(), networkSwarm: json("networkSwarm").$type(), stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }), + customNetworkIds: text("customNetworkIds").array(), replicas: integer("replicas").default(1).notNull(), createdAt: text("createdAt") .notNull() @@ -130,6 +131,7 @@ const createSchema = createInsertSchema(mariadb, { labelsSwarm: LabelsSwarmSchema.nullable(), networkSwarm: NetworkSwarmSchema.nullable(), stopGracePeriodSwarm: z.bigint().nullable(), + customNetworkIds: z.array(z.string()).nullable(), }); export const apiCreateMariaDB = createSchema diff --git a/packages/server/src/db/schema/mongo.ts b/packages/server/src/db/schema/mongo.ts index c1ee4185b2..df56ebc3cd 100644 --- a/packages/server/src/db/schema/mongo.ts +++ b/packages/server/src/db/schema/mongo.ts @@ -66,6 +66,7 @@ export const mongo = pgTable("mongo", { labelsSwarm: json("labelsSwarm").$type(), networkSwarm: json("networkSwarm").$type(), stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }), + customNetworkIds: text("customNetworkIds").array(), replicas: integer("replicas").default(1).notNull(), createdAt: text("createdAt") .notNull() @@ -127,6 +128,7 @@ const createSchema = createInsertSchema(mongo, { labelsSwarm: LabelsSwarmSchema.nullable(), networkSwarm: NetworkSwarmSchema.nullable(), stopGracePeriodSwarm: z.bigint().nullable(), + customNetworkIds: z.array(z.string()).nullable(), }); export const apiCreateMongo = createSchema diff --git a/packages/server/src/db/schema/mysql.ts b/packages/server/src/db/schema/mysql.ts index c900489802..08be65fab9 100644 --- a/packages/server/src/db/schema/mysql.ts +++ b/packages/server/src/db/schema/mysql.ts @@ -61,6 +61,7 @@ export const mysql = pgTable("mysql", { labelsSwarm: json("labelsSwarm").$type(), networkSwarm: json("networkSwarm").$type(), stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }), + customNetworkIds: text("customNetworkIds").array(), replicas: integer("replicas").default(1).notNull(), createdAt: text("createdAt") .notNull() @@ -127,6 +128,7 @@ const createSchema = createInsertSchema(mysql, { labelsSwarm: LabelsSwarmSchema.nullable(), networkSwarm: NetworkSwarmSchema.nullable(), stopGracePeriodSwarm: z.bigint().nullable(), + customNetworkIds: z.array(z.string()).nullable(), }); export const apiCreateMySql = createSchema diff --git a/packages/server/src/db/schema/postgres.ts b/packages/server/src/db/schema/postgres.ts index 375f7a9c03..722aad9421 100644 --- a/packages/server/src/db/schema/postgres.ts +++ b/packages/server/src/db/schema/postgres.ts @@ -61,6 +61,7 @@ export const postgres = pgTable("postgres", { labelsSwarm: json("labelsSwarm").$type(), networkSwarm: json("networkSwarm").$type(), stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }), + customNetworkIds: text("customNetworkIds").array(), replicas: integer("replicas").default(1).notNull(), createdAt: text("createdAt") .notNull() @@ -120,6 +121,7 @@ const createSchema = createInsertSchema(postgres, { labelsSwarm: LabelsSwarmSchema.nullable(), networkSwarm: NetworkSwarmSchema.nullable(), stopGracePeriodSwarm: z.bigint().nullable(), + customNetworkIds: z.array(z.string()).nullable(), }); export const apiCreatePostgres = createSchema diff --git a/packages/server/src/db/schema/redis.ts b/packages/server/src/db/schema/redis.ts index 0cb6dd484f..5dda08d9cf 100644 --- a/packages/server/src/db/schema/redis.ts +++ b/packages/server/src/db/schema/redis.ts @@ -5,7 +5,6 @@ import { nanoid } from "nanoid"; import { z } from "zod"; import { environments } from "./environment"; import { mounts } from "./mount"; -import { projects } from "./project"; import { server } from "./server"; import { applicationStatus, @@ -61,6 +60,7 @@ export const redis = pgTable("redis", { labelsSwarm: json("labelsSwarm").$type(), networkSwarm: json("networkSwarm").$type(), stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }), + customNetworkIds: text("customNetworkIds").array(), replicas: integer("replicas").default(1).notNull(), environmentId: text("environmentId") @@ -110,6 +110,7 @@ const createSchema = createInsertSchema(redis, { labelsSwarm: LabelsSwarmSchema.nullable(), networkSwarm: NetworkSwarmSchema.nullable(), stopGracePeriodSwarm: z.bigint().nullable(), + customNetworkIds: z.array(z.string()).nullable(), }); export const apiCreateRedis = createSchema From 62ff674f7482c88eb80fe58fff7de6efb542b7c1 Mon Sep 17 00:00:00 2001 From: Antoine Date: Fri, 10 Oct 2025 14:23:55 +0200 Subject: [PATCH 03/51] feat(services): add network service layer and docker utilities --- packages/server/src/services/network.ts | 811 ++++++++++++++++++ .../server/src/utils/docker/network-utils.ts | 281 ++++++ 2 files changed, 1092 insertions(+) create mode 100644 packages/server/src/services/network.ts create mode 100644 packages/server/src/utils/docker/network-utils.ts diff --git a/packages/server/src/services/network.ts b/packages/server/src/services/network.ts new file mode 100644 index 0000000000..6c8980def1 --- /dev/null +++ b/packages/server/src/services/network.ts @@ -0,0 +1,811 @@ +import { db } from "@dokploy/server/db"; +import { + type apiCreateNetwork, + applications, + compose, + mariadb, + mongo, + mysql, + networks, + postgres, + redis, +} from "@dokploy/server/db/schema"; +import { TRPCError } from "@trpc/server"; +import { and, desc, eq, inArray } from "drizzle-orm"; +import { + createDockerNetwork, + type DockerNetworkConfig, + dockerNetworkExists, + ensureTraefikConnectedToNetwork, + inspectDockerNetwork, + listDockerNetworks, + removeDockerNetwork, +} from "../utils/docker/network-utils"; + +export type DokployNetwork = typeof networks.$inferSelect; + +/** + * Create a new custom network + */ +export const createNetwork = async (input: typeof apiCreateNetwork._type) => { + // Validate required fields + if (!input.organizationId) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Organization ID is required", + }); + } + + try { + // Check if network name already exists for this organization + const existing = await db.query.networks.findFirst({ + where: and( + eq(networks.organizationId, input.organizationId), + eq(networks.networkName, input.networkName), + ), + }); + + if (existing) { + throw new TRPCError({ + code: "CONFLICT", + message: `Network with name '${input.networkName}' already exists in this organization`, + }); + } + + // Check if Docker network with this name exists + const dockerExists = await dockerNetworkExists( + input.networkName, + input.serverId, + ); + + if (dockerExists) { + throw new TRPCError({ + code: "CONFLICT", + message: `Docker network '${input.networkName}' already exists on this server`, + }); + } + + // Build Docker network configuration + const networkConfig: DockerNetworkConfig = { + Name: input.networkName, + Driver: input.driver || "bridge", + CheckDuplicate: true, + Attachable: input.attachable, + Internal: input.internal, + Labels: { + "com.dokploy.organization.id": input.organizationId, + "com.dokploy.network.name": input.name, + ...(input.projectId && { "com.dokploy.project.id": input.projectId }), + }, + }; + + // Add IPAM configuration if provided + if (input.subnet || input.gateway || input.ipRange) { + networkConfig.IPAM = { + Config: [ + { + Subnet: input.subnet || undefined, + Gateway: input.gateway || undefined, + IPRange: input.ipRange || undefined, + }, + ], + }; + } + + // Create Docker network + const dockerNetwork = await createDockerNetwork( + networkConfig, + input.serverId, + ); + const dockerNetworkInspect = await inspectDockerNetwork( + input.networkName, + input.serverId, + ); + + // If this should be the default network, unset other defaults + if (input.isDefault) { + await db + .update(networks) + .set({ isDefault: false }) + .where( + and( + eq(networks.organizationId, input.organizationId), + eq(networks.isDefault, true), + ), + ); + } + + // Create network record in database + const newNetwork = await db + .insert(networks) + .values({ + name: input.name, + description: input.description, + networkName: input.networkName, + driver: input.driver || "bridge", + isDefault: input.isDefault || false, + subnet: input.subnet, + gateway: input.gateway, + ipRange: input.ipRange, + attachable: input.attachable ?? true, + internal: input.internal ?? false, + organizationId: input.organizationId, + projectId: input.projectId, + serverId: input.serverId, + dockerNetworkId: dockerNetworkInspect.Id, + }) + .returning() + .then((value: DokployNetwork[]) => value[0]); + + if (!newNetwork) { + // Rollback: Remove Docker network if DB insert fails + await removeDockerNetwork(input.networkName, input.serverId); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to create network record", + }); + } + + return newNetwork; + } catch (error) { + console.error("Error creating network:", error); + throw error; + } +}; + +/** + * Get a network by ID + */ +export const findNetworkById = async (networkId: string) => { + const network = await db.query.networks.findFirst({ + where: eq(networks.networkId, networkId), + with: { + project: true, + server: true, + }, + }); + + if (!network) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Network not found", + }); + } + + return network; +}; + +/** + * Get all networks for a project + */ +export const findNetworksByProjectId = async (projectId: string) => { + return await db.query.networks.findMany({ + where: eq(networks.projectId, projectId), + with: { + server: true, + }, + orderBy: desc(networks.createdAt), + }); +}; + +/** + * Get all networks for an organization + */ +export const findNetworksByOrganizationId = async (organizationId: string) => { + return await db.query.networks.findMany({ + where: eq(networks.organizationId, organizationId), + with: { + server: true, + project: true, + }, + orderBy: desc(networks.createdAt), + }); +}; + +/** + * Update a network + */ +export const updateNetwork = async ( + networkId: string, + data: Partial, +) => { + const network = await findNetworkById(networkId); + + // If setting as default, unset other defaults in organization + if (data.isDefault) { + await db + .update(networks) + .set({ isDefault: false }) + .where( + and( + eq(networks.organizationId, network.organizationId), + eq(networks.isDefault, true), + ), + ); + } + + const updated = await db + .update(networks) + .set({ + ...data, + networkId, + }) + .where(eq(networks.networkId, networkId)) + .returning() + .then((res: DokployNetwork[]) => res[0]); + + return updated; +}; + +/** + * Delete a network + */ +export const deleteNetwork = async (networkId: string) => { + const network = await findNetworkById(networkId); + + // Check if network is being used by any resources + const usageChecks = await Promise.all([ + db.query.applications.findFirst({ + where: eq(applications.customNetworkIds, [networkId]), + }), + db.query.compose.findFirst({ + where: eq(compose.customNetworkIds, [networkId]), + }), + db.query.postgres.findFirst({ + where: eq(postgres.customNetworkIds, [networkId]), + }), + db.query.mysql.findFirst({ + where: eq(mysql.customNetworkIds, [networkId]), + }), + db.query.mariadb.findFirst({ + where: eq(mariadb.customNetworkIds, [networkId]), + }), + db.query.mongo.findFirst({ + where: eq(mongo.customNetworkIds, [networkId]), + }), + db.query.redis.findFirst({ + where: eq(redis.customNetworkIds, [networkId]), + }), + ]); + + const inUse = usageChecks.some((check: any) => check !== undefined); + + if (inUse) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: + "Cannot delete network that is in use. Please remove it from all resources first.", + }); + } + + // Remove Docker network + try { + await removeDockerNetwork(network.networkName, network.serverId); + } catch (error) { + console.warn("Failed to remove Docker network, continuing:", error); + // Continue with database deletion even if Docker removal fails + } + + // Delete network record + const deleted = await db + .delete(networks) + .where(eq(networks.networkId, networkId)) + .returning() + .then((res: DokployNetwork[]) => res[0]); + + return deleted; +}; + +/** + * Assign a network to a resource (application, compose, database) + */ +export const assignNetworkToResource = async ( + networkId: string, + resourceId: string, + resourceType: + | "application" + | "compose" + | "postgres" + | "mysql" + | "mariadb" + | "mongo" + | "redis", +) => { + const network = await findNetworkById(networkId); + + const tableMap = { + application: applications, + compose: compose, + postgres: postgres, + mysql: mysql, + mariadb: mariadb, + mongo: mongo, + redis: redis, + }; + + const table = tableMap[resourceType]; + const idField = `${resourceType}Id` as keyof typeof table.$inferSelect; + + // Get current resource using type-specific queries + let resource: any; + if (resourceType === "application") { + resource = await db.query.applications.findFirst({ + where: eq(applications.applicationId, resourceId), + }); + } else if (resourceType === "compose") { + resource = await db.query.compose.findFirst({ + where: eq(compose.composeId, resourceId), + }); + } else if (resourceType === "postgres") { + resource = await db.query.postgres.findFirst({ + where: eq(postgres.postgresId, resourceId), + }); + } else if (resourceType === "mysql") { + resource = await db.query.mysql.findFirst({ + where: eq(mysql.mysqlId, resourceId), + }); + } else if (resourceType === "mariadb") { + resource = await db.query.mariadb.findFirst({ + where: eq(mariadb.mariadbId, resourceId), + }); + } else if (resourceType === "mongo") { + resource = await db.query.mongo.findFirst({ + where: eq(mongo.mongoId, resourceId), + }); + } else { + resource = await db.query.redis.findFirst({ + where: eq(redis.redisId, resourceId), + }); + } + + if (!resource) { + throw new TRPCError({ + code: "NOT_FOUND", + message: `${resourceType} not found`, + }); + } + + // Get current network IDs + const currentNetworkIds = (resource.customNetworkIds || []) as string[]; + + // Check if already assigned + if (currentNetworkIds.includes(networkId)) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Network already assigned to this resource", + }); + } + + // Validate network driver compatibility + // Swarm services (applications, databases) require overlay networks + // Compose services depend on composeType: "stack" requires overlay, "docker-compose" allows both + const isSwarmService = resourceType !== "compose"; + const isComposeStack = + resourceType === "compose" && resource.composeType === "stack"; + + if ((isSwarmService || isComposeStack) && network.driver !== "overlay") { + throw new TRPCError({ + code: "BAD_REQUEST", + message: `This ${resourceType} requires an overlay network. Bridge networks are only compatible with Docker Compose services in "docker-compose" mode.`, + }); + } + + // Add network ID + const updatedNetworkIds = [...currentNetworkIds, networkId]; + + // Update resource + await db + .update(table) + .set({ + customNetworkIds: updatedNetworkIds, + }) + .where(eq(table[idField], resourceId)); + + // If resource is running, connect it to the network + // This will be handled by deployment logic + + return { success: true, networkId, resourceId, resourceType }; +}; + +/** + * Remove a network from a resource + */ +export const removeNetworkFromResource = async ( + networkId: string, + resourceId: string, + resourceType: + | "application" + | "compose" + | "postgres" + | "mysql" + | "mariadb" + | "mongo" + | "redis", +) => { + const tableMap = { + application: applications, + compose: compose, + postgres: postgres, + mysql: mysql, + mariadb: mariadb, + mongo: mongo, + redis: redis, + }; + + const table = tableMap[resourceType]; + const idField = `${resourceType}Id` as keyof typeof table.$inferSelect; + + // Get current resource using type-specific queries + let resource: any; + if (resourceType === "application") { + resource = await db.query.applications.findFirst({ + where: eq(applications.applicationId, resourceId), + }); + } else if (resourceType === "compose") { + resource = await db.query.compose.findFirst({ + where: eq(compose.composeId, resourceId), + }); + } else if (resourceType === "postgres") { + resource = await db.query.postgres.findFirst({ + where: eq(postgres.postgresId, resourceId), + }); + } else if (resourceType === "mysql") { + resource = await db.query.mysql.findFirst({ + where: eq(mysql.mysqlId, resourceId), + }); + } else if (resourceType === "mariadb") { + resource = await db.query.mariadb.findFirst({ + where: eq(mariadb.mariadbId, resourceId), + }); + } else if (resourceType === "mongo") { + resource = await db.query.mongo.findFirst({ + where: eq(mongo.mongoId, resourceId), + }); + } else { + resource = await db.query.redis.findFirst({ + where: eq(redis.redisId, resourceId), + }); + } + + if (!resource) { + throw new TRPCError({ + code: "NOT_FOUND", + message: `${resourceType} not found`, + }); + } + + // Get current network IDs + const currentNetworkIds = (resource.customNetworkIds || []) as string[]; + + // Remove network ID + const updatedNetworkIds = currentNetworkIds.filter((id) => id !== networkId); + + // Update resource + await db + .update(table) + .set({ + customNetworkIds: updatedNetworkIds, + }) + .where(eq(table[idField], resourceId)); + + return { success: true, networkId, resourceId, resourceType }; +}; + +/** + * Get networks assigned to a resource + */ +export const getResourceNetworks = async ( + resourceId: string, + resourceType: + | "application" + | "compose" + | "postgres" + | "mysql" + | "mariadb" + | "mongo" + | "redis", +) => { + const tableMap = { + application: applications, + compose: compose, + postgres: postgres, + mysql: mysql, + mariadb: mariadb, + mongo: mongo, + redis: redis, + }; + + const table = tableMap[resourceType]; + const idField = `${resourceType}Id` as keyof typeof table.$inferSelect; + + // Get current resource using type-specific queries + let resource: any; + if (resourceType === "application") { + resource = await db.query.applications.findFirst({ + where: eq(applications.applicationId, resourceId), + }); + } else if (resourceType === "compose") { + resource = await db.query.compose.findFirst({ + where: eq(compose.composeId, resourceId), + }); + } else if (resourceType === "postgres") { + resource = await db.query.postgres.findFirst({ + where: eq(postgres.postgresId, resourceId), + }); + } else if (resourceType === "mysql") { + resource = await db.query.mysql.findFirst({ + where: eq(mysql.mysqlId, resourceId), + }); + } else if (resourceType === "mariadb") { + resource = await db.query.mariadb.findFirst({ + where: eq(mariadb.mariadbId, resourceId), + }); + } else if (resourceType === "mongo") { + resource = await db.query.mongo.findFirst({ + where: eq(mongo.mongoId, resourceId), + }); + } else { + resource = await db.query.redis.findFirst({ + where: eq(redis.redisId, resourceId), + }); + } + + if (!resource) { + throw new TRPCError({ + code: "NOT_FOUND", + message: `${resourceType} not found`, + }); + } + + const networkIds = (resource.customNetworkIds || []) as string[]; + + if (networkIds.length === 0) { + return []; + } + + // Get network details + const networkDetails = await db.query.networks.findMany({ + where: inArray(networks.networkId, networkIds), + }); + + return networkDetails; +}; + +/** + * List all Docker networks on a server + */ +export const listServerNetworks = async (serverId?: string | null) => { + return await listDockerNetworks(serverId); +}; + +/** + * Sync Dokploy networks with Docker networks + * This ensures the database is in sync with Docker state + */ +export const syncNetworks = async (serverId?: string | null) => { + const dockerNetworks = await listDockerNetworks(serverId); + const dokployNetworks = await db.query.networks.findMany({ + where: serverId ? eq(networks.serverId, serverId) : undefined, + }); + + const synced = { + missing: [] as string[], + orphaned: [] as string[], + }; + + // Check for missing Docker networks + for (const dokployNetwork of dokployNetworks) { + const exists = dockerNetworks.some( + (dn: any) => dn.Name === dokployNetwork.networkName, + ); + if (!exists) { + synced.missing.push(dokployNetwork.networkName); + } + } + + // Check for orphaned Docker networks (created by Dokploy but not in DB) + for (const dockerNetwork of dockerNetworks) { + if (dockerNetwork.Labels?.["com.dokploy.project.id"]) { + const exists = dokployNetworks.some( + (dn: any) => dn.networkName === dockerNetwork.Name, + ); + if (!exists) { + synced.orphaned.push(dockerNetwork.Name); + } + } + } + + return synced; +}; + +/** + * Find or create an isolated network for a compose service + * This is used for the isolatedDeployment feature + */ +export const findOrCreateIsolatedNetwork = async ({ + organizationId, + projectId, + appName, + serverId = null, +}: { + organizationId: string; + projectId?: string; + appName: string; + serverId?: string | null; +}): Promise => { + // Try to find existing network with this appName + const existing = await db.query.networks.findFirst({ + where: and( + eq(networks.organizationId, organizationId), + eq(networks.networkName, appName), + ), + }); + + if (existing) { + return existing; + } + + // Create new isolated network + const driver = serverId ? "overlay" : "bridge"; + + // Insert into database first to get the networkId + const [network] = await db + .insert(networks) + .values({ + name: `Isolated Network (${appName})`, + networkName: appName, + description: `Auto-generated isolated network for ${appName}`, + driver, + organizationId, + projectId, + serverId, + isDefault: false, + attachable: true, + internal: false, + }) + .returning(); + + if (!network) { + throw new Error("Failed to create isolated network in database"); + } + + // Create network in Docker with the database-generated networkId + const dockerConfig: DockerNetworkConfig = { + Name: appName, + Driver: driver, + Attachable: true, + Internal: false, + Labels: { + "com.dokploy.network.id": network.networkId, + "com.dokploy.organization.id": organizationId, + "com.dokploy.isolated": "true", + ...(projectId && { "com.dokploy.project.id": projectId }), + }, + }; + + await createDockerNetwork(dockerConfig, serverId); + const dockerNetworkInspect = await inspectDockerNetwork(appName, serverId); + + // Update the network with the Docker network ID + const [updatedNetwork] = await db + .update(networks) + .set({ dockerNetworkId: dockerNetworkInspect.Id }) + .where(eq(networks.networkId, network.networkId)) + .returning(); + + return updatedNetwork || network; +}; + +/** + * Connect Traefik to all custom networks assigned to a resource + * This is called when a domain is added to a resource to ensure Traefik can route traffic + */ +export const connectTraefikToResourceNetworks = async ( + resourceId: string, + resourceType: + | "application" + | "compose" + | "postgres" + | "mysql" + | "mariadb" + | "mongo" + | "redis", + serverId?: string | null, +): Promise => { + try { + // Get the resource with its customNetworkIds + let customNetworkIds: string[] = []; + + if (resourceType === "application") { + const app = await db.query.applications.findFirst({ + where: eq(applications.applicationId, resourceId), + }); + customNetworkIds = (app?.customNetworkIds || []) as string[]; + } else if (resourceType === "compose") { + const comp = await db.query.compose.findFirst({ + where: eq(compose.composeId, resourceId), + }); + customNetworkIds = (comp?.customNetworkIds || []) as string[]; + } else if (resourceType === "postgres") { + const pg = await db.query.postgres.findFirst({ + where: eq(postgres.postgresId, resourceId), + }); + customNetworkIds = (pg?.customNetworkIds || []) as string[]; + } else if (resourceType === "mysql") { + const mys = await db.query.mysql.findFirst({ + where: eq(mysql.mysqlId, resourceId), + }); + customNetworkIds = (mys?.customNetworkIds || []) as string[]; + } else if (resourceType === "mariadb") { + const maria = await db.query.mariadb.findFirst({ + where: eq(mariadb.mariadbId, resourceId), + }); + customNetworkIds = (maria?.customNetworkIds || []) as string[]; + } else if (resourceType === "mongo") { + const mng = await db.query.mongo.findFirst({ + where: eq(mongo.mongoId, resourceId), + }); + customNetworkIds = (mng?.customNetworkIds || []) as string[]; + } else { + const rd = await db.query.redis.findFirst({ + where: eq(redis.redisId, resourceId), + }); + customNetworkIds = (rd?.customNetworkIds || []) as string[]; + } + + // If no custom networks assigned, nothing to do (will use dokploy-network) + if (!customNetworkIds || customNetworkIds.length === 0) { + console.log( + `No custom networks for ${resourceType} ${resourceId}, skipping Traefik connection`, + ); + return; + } + + // Get network details from DB + const customNetworks = await db.query.networks.findMany({ + where: inArray(networks.networkId, customNetworkIds), + }); + + if (customNetworks.length === 0) { + console.warn( + `Custom networks not found in DB for ${resourceType} ${resourceId}`, + ); + return; + } + + console.log( + `Connecting Traefik to ${customNetworks.length} networks for ${resourceType} ${resourceId}`, + ); + + // Connect Traefik to each custom network + const results = await Promise.allSettled( + customNetworks.map((network) => + ensureTraefikConnectedToNetwork(network.networkName, serverId), + ), + ); + + // Log results + for (let i = 0; i < results.length; i++) { + const result = results[i]; + const networkName = customNetworks[i]?.networkName; + + if (!result) continue; + + if (result.status === "fulfilled") { + console.log(`✅ Traefik connected to network: ${networkName}`); + } else { + console.warn( + `⚠️ Failed to connect Traefik to network ${networkName}:`, + "reason" in result ? result.reason : "Unknown error", + ); + // Don't throw - this shouldn't block deployment + } + } + } catch (error) { + console.error( + `Error connecting Traefik to networks for ${resourceType} ${resourceId}:`, + error, + ); + // Don't throw - network connection failures shouldn't block deployments + } +}; diff --git a/packages/server/src/utils/docker/network-utils.ts b/packages/server/src/utils/docker/network-utils.ts new file mode 100644 index 0000000000..5b6b6e155e --- /dev/null +++ b/packages/server/src/utils/docker/network-utils.ts @@ -0,0 +1,281 @@ +import type { Network } from "dockerode"; +import { execAsync, execAsyncRemote } from "../process/execAsync"; +import { getRemoteDocker } from "../servers/remote-docker"; + +export interface IPAMConfig { + Subnet?: string; + Gateway?: string; + IPRange?: string; +} + +export interface DockerNetworkConfig { + Name: string; + Driver: "bridge" | "overlay"; + CheckDuplicate?: boolean; + Internal?: boolean; + Attachable?: boolean; + EnableIPv6?: boolean; + IPAM?: { + Config?: IPAMConfig[]; + }; + Labels?: Record; + Options?: Record; +} + +export const createDockerNetwork = async ( + config: DockerNetworkConfig, + serverId?: string | null, +): Promise => { + try { + const remoteDocker = await getRemoteDocker(serverId); + const network = await remoteDocker.createNetwork( + config as Parameters[0], + ); + return network; + } catch (error) { + console.error("Error creating Docker network:", error); + throw error; + } +}; + +export const removeDockerNetwork = async ( + networkName: string, + serverId?: string | null, +): Promise => { + try { + const remoteDocker = await getRemoteDocker(serverId); + const network = remoteDocker.getNetwork(networkName); + await network.remove(); + } catch (error) { + console.error("Error removing Docker network:", error); + throw error; + } +}; + +export const dockerNetworkExists = async ( + networkName: string, + serverId?: string | null, +): Promise => { + try { + const remoteDocker = await getRemoteDocker(serverId); + const network = remoteDocker.getNetwork(networkName); + await network.inspect(); + return true; + } catch { + return false; + } +}; + +export const inspectDockerNetwork = async ( + networkName: string, + serverId?: string | null, +) => { + try { + const remoteDocker = await getRemoteDocker(serverId); + const network = remoteDocker.getNetwork(networkName); + return await network.inspect(); + } catch (error) { + console.error("Error inspecting Docker network:", error); + throw error; + } +}; + +export const listDockerNetworks = async (serverId?: string | null) => { + try { + const remoteDocker = await getRemoteDocker(serverId); + return await remoteDocker.listNetworks(); + } catch (error) { + console.error("Error listing Docker networks:", error); + throw error; + } +}; + +export const connectContainerToNetwork = async ( + networkName: string, + containerName: string, + aliases?: string[], + serverId?: string | null, +): Promise => { + try { + const remoteDocker = await getRemoteDocker(serverId); + const network = remoteDocker.getNetwork(networkName); + + await network.connect({ + Container: containerName, + EndpointConfig: aliases + ? { + Aliases: aliases, + } + : undefined, + }); + } catch (error) { + console.error("Error connecting container to network:", error); + throw error; + } +}; + +export const disconnectContainerFromNetwork = async ( + networkName: string, + containerName: string, + serverId?: string | null, +): Promise => { + try { + const remoteDocker = await getRemoteDocker(serverId); + const network = remoteDocker.getNetwork(networkName); + + await network.disconnect({ + Container: containerName, + Force: false, + }); + } catch (error) { + console.error("Error disconnecting container from network:", error); + throw error; + } +}; + +export const connectServiceToNetwork = async ( + networkName: string, + serviceName: string, + serverId?: string | null, +): Promise => { + try { + const remoteDocker = await getRemoteDocker(serverId); + const service = remoteDocker.getService(serviceName); + const inspect = await service.inspect(); + + const currentNetworks = inspect.Spec.TaskTemplate?.Networks || []; + const alreadyConnected = currentNetworks.some( + (net: { Target?: string }) => net.Target === networkName, + ); + + if (alreadyConnected) { + console.log(`Service ${serviceName} already connected to ${networkName}`); + return; + } + + const updatedNetworks = [...currentNetworks, { Target: networkName }]; + + await service.update({ + version: Number.parseInt(inspect.Version.Index), + ...inspect.Spec, + TaskTemplate: { + ...inspect.Spec.TaskTemplate, + Networks: updatedNetworks, + }, + }); + } catch (error) { + console.error("Error connecting service to network:", error); + throw error; + } +}; + +export const disconnectServiceFromNetwork = async ( + networkName: string, + serviceName: string, + serverId?: string | null, +): Promise => { + try { + const remoteDocker = await getRemoteDocker(serverId); + const service = remoteDocker.getService(serviceName); + const inspect = await service.inspect(); + + const currentNetworks = inspect.Spec.TaskTemplate?.Networks || []; + const updatedNetworks = currentNetworks.filter( + (net: { Target?: string }) => net.Target !== networkName, + ); + + await service.update({ + version: Number.parseInt(inspect.Version.Index), + ...inspect.Spec, + TaskTemplate: { + ...inspect.Spec.TaskTemplate, + Networks: updatedNetworks, + }, + }); + } catch (error) { + console.error("Error disconnecting service from network:", error); + throw error; + } +}; + +export const getContainerNetworks = async ( + containerName: string, + serverId?: string | null, +): Promise => { + try { + const remoteDocker = await getRemoteDocker(serverId); + const container = remoteDocker.getContainer(containerName); + const inspect = await container.inspect(); + + return Object.keys(inspect.NetworkSettings.Networks || {}); + } catch (error) { + console.error("Error getting container networks:", error); + return []; + } +}; + +export const ensureTraefikConnectedToNetwork = async ( + networkName: string, + serverId?: string | null, +): Promise => { + try { + const traefikService = "dokploy-traefik"; + + const networkExists = await dockerNetworkExists(networkName, serverId); + if (!networkExists) { + console.warn(`Network ${networkName} does not exist`); + return; + } + + try { + await connectServiceToNetwork(networkName, traefikService, serverId); + console.log(`Traefik service connected to network ${networkName}`); + return; + } catch (swarmError) { + console.log("Traefik not running as service, trying container mode"); + } + + try { + const networks = await getContainerNetworks(traefikService, serverId); + if (networks.includes(networkName)) { + console.log(`Traefik container already connected to ${networkName}`); + return; + } + + await connectContainerToNetwork( + networkName, + traefikService, + [], + serverId, + ); + console.log(`Traefik container connected to network ${networkName}`); + } catch (containerError) { + console.error("Failed to connect Traefik to network:", containerError); + throw new Error( + `Could not connect Traefik to network ${networkName}. Ensure Traefik is running.`, + ); + } + } catch (error) { + console.error("Error ensuring Traefik network connection:", error); + throw error; + } +}; + +export const pruneUnusedNetworks = async ( + serverId?: string | null, +): Promise => { + try { + const command = "docker network prune --force"; + + if (serverId) { + await execAsyncRemote(serverId, command); + } else { + await execAsync(command); + } + + console.log("Unused Docker networks pruned"); + } catch (error) { + console.error("Error pruning unused networks:", error); + throw error; + } +}; From 67b67731030c262eccad7b580443258a6403af73 Mon Sep 17 00:00:00 2001 From: Antoine Date: Fri, 10 Oct 2025 14:29:02 +0200 Subject: [PATCH 04/51] feat(api): add network tRPC router with resource filtering --- apps/dokploy/server/api/root.ts | 2 + apps/dokploy/server/api/routers/network.ts | 249 +++++++++++++++++++++ 2 files changed, 251 insertions(+) create mode 100644 apps/dokploy/server/api/routers/network.ts diff --git a/apps/dokploy/server/api/root.ts b/apps/dokploy/server/api/root.ts index 63ce38d107..37a390102a 100644 --- a/apps/dokploy/server/api/root.ts +++ b/apps/dokploy/server/api/root.ts @@ -20,6 +20,7 @@ import { mariadbRouter } from "./routers/mariadb"; import { mongoRouter } from "./routers/mongo"; import { mountRouter } from "./routers/mount"; import { mysqlRouter } from "./routers/mysql"; +import { networkRouter } from "./routers/network"; import { notificationRouter } from "./routers/notification"; import { organizationRouter } from "./routers/organization"; import { portRouter } from "./routers/port"; @@ -63,6 +64,7 @@ export const appRouter = createTRPCRouter({ deployment: deploymentRouter, previewDeployment: previewDeploymentRouter, mounts: mountRouter, + network: networkRouter, certificates: certificateRouter, settings: settingsRouter, security: securityRouter, diff --git a/apps/dokploy/server/api/routers/network.ts b/apps/dokploy/server/api/routers/network.ts new file mode 100644 index 0000000000..8bbd819a8d --- /dev/null +++ b/apps/dokploy/server/api/routers/network.ts @@ -0,0 +1,249 @@ +import { + assignNetworkToResource, + createNetwork, + deleteNetwork, + findNetworkById, + findNetworksByOrganizationId, + getResourceNetworks, + listServerNetworks, + removeNetworkFromResource, + syncNetworks, + updateNetwork, +} from "@dokploy/server"; +import { TRPCError } from "@trpc/server"; +import { z } from "zod"; +import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; +import { + apiCreateNetwork, + apiFindOneNetwork, + apiRemoveNetwork, + apiUpdateNetwork, +} from "@/server/db/schema"; + +const RESOURCE_TYPE_ENUM = z.enum([ + "application", + "compose", + "postgres", + "mysql", + "mariadb", + "mongo", + "redis", +] as const); + +export const networkRouter = createTRPCRouter({ + create: protectedProcedure + .input(apiCreateNetwork) + .mutation(async ({ input, ctx }) => { + const networkInput = { + ...input, + organizationId: ctx.session.activeOrganizationId, + }; + + try { + return await createNetwork(networkInput); + } catch (error) { + console.error("Error creating network:", error); + if (error instanceof TRPCError) { + throw error; + } + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to create network", + cause: error, + }); + } + }), + + one: protectedProcedure.input(apiFindOneNetwork).query(async ({ input }) => { + return await findNetworkById(input.networkId); + }), + + all: protectedProcedure.query(async ({ ctx }) => { + return await findNetworksByOrganizationId(ctx.session.activeOrganizationId); + }), + + allForResource: protectedProcedure + .input( + z.object({ + resourceType: RESOURCE_TYPE_ENUM, + composeType: z.enum(["docker-compose", "stack"]).optional(), + }), + ) + .query(async ({ input, ctx }) => { + const networks = await findNetworksByOrganizationId( + ctx.session.activeOrganizationId, + ); + + if ( + input.resourceType === "compose" && + input.composeType === "docker-compose" + ) { + return networks; + } + + return networks.filter((network) => network.driver === "overlay"); + }), + + update: protectedProcedure + .input(apiUpdateNetwork) + .mutation(async ({ input, ctx }) => { + const network = await findNetworkById(input.networkId); + + if (network.organizationId !== ctx.session.activeOrganizationId) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "You do not have access to this network", + }); + } + + try { + return await updateNetwork(input.networkId, input); + } catch (error) { + console.error("Error updating network:", error); + if (error instanceof TRPCError) { + throw error; + } + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to update network", + cause: error, + }); + } + }), + + delete: protectedProcedure + .input(apiRemoveNetwork) + .mutation(async ({ input, ctx }) => { + const network = await findNetworkById(input.networkId); + + if (network.organizationId !== ctx.session.activeOrganizationId) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "You do not have access to this network", + }); + } + + try { + return await deleteNetwork(input.networkId); + } catch (error) { + console.error("Error deleting network:", error); + if (error instanceof TRPCError) { + throw error; + } + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to delete network", + cause: error, + }); + } + }), + + assignToResource: protectedProcedure + .input( + z.object({ + networkId: z.string().min(1), + resourceId: z.string().min(1), + resourceType: RESOURCE_TYPE_ENUM, + }), + ) + .mutation(async ({ input, ctx }) => { + const network = await findNetworkById(input.networkId); + + if (network.organizationId !== ctx.session.activeOrganizationId) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "You do not have access to this network", + }); + } + + try { + return await assignNetworkToResource( + input.networkId, + input.resourceId, + input.resourceType, + ); + } catch (error) { + console.error("Error assigning network to resource:", error); + if (error instanceof TRPCError) { + throw error; + } + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to assign network to resource", + cause: error, + }); + } + }), + + removeFromResource: protectedProcedure + .input( + z.object({ + networkId: z.string().min(1), + resourceId: z.string().min(1), + resourceType: RESOURCE_TYPE_ENUM, + }), + ) + .mutation(async ({ input, ctx }) => { + const network = await findNetworkById(input.networkId); + + if (network.organizationId !== ctx.session.activeOrganizationId) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "You do not have access to this network", + }); + } + + try { + return await removeNetworkFromResource( + input.networkId, + input.resourceId, + input.resourceType, + ); + } catch (error) { + console.error("Error removing network from resource:", error); + if (error instanceof TRPCError) { + throw error; + } + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to remove network from resource", + cause: error, + }); + } + }), + + getResourceNetworks: protectedProcedure + .input( + z.object({ + resourceId: z.string().min(1), + resourceType: RESOURCE_TYPE_ENUM, + }), + ) + .query(async ({ input }) => { + return await getResourceNetworks(input.resourceId, input.resourceType); + }), + + listAvailableForOrganization: protectedProcedure.query(async ({ ctx }) => { + return await findNetworksByOrganizationId(ctx.session.activeOrganizationId); + }), + + listServerNetworks: protectedProcedure + .input( + z.object({ + serverId: z.string().nullable().optional(), + }), + ) + .query(async ({ input }) => { + return await listServerNetworks(input.serverId); + }), + + syncNetworks: protectedProcedure + .input( + z.object({ + serverId: z.string().nullable().optional(), + }), + ) + .mutation(async ({ input }) => { + return await syncNetworks(input.serverId); + }), +}); From e00e6127bd278196181b9b4839cee0bd05157091 Mon Sep 17 00:00:00 2001 From: Antoine Date: Fri, 10 Oct 2025 14:34:41 +0200 Subject: [PATCH 05/51] feat(docker): add custom network support to resource builders --- packages/server/src/utils/builders/compose.ts | 33 +++++++++++++++---- packages/server/src/utils/builders/index.ts | 2 +- .../server/src/utils/databases/mariadb.ts | 2 +- packages/server/src/utils/databases/mongo.ts | 2 +- packages/server/src/utils/databases/mysql.ts | 2 +- .../server/src/utils/databases/postgres.ts | 5 ++- packages/server/src/utils/databases/redis.ts | 2 +- 7 files changed, 33 insertions(+), 15 deletions(-) diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts index 667b46b745..6d11e2675a 100644 --- a/packages/server/src/utils/builders/compose.ts +++ b/packages/server/src/utils/builders/compose.ts @@ -6,18 +6,23 @@ import { } from "node:fs"; import { dirname, join } from "node:path"; import { paths } from "@dokploy/server/constants"; +import { + assignNetworkToResource, + findOrCreateIsolatedNetwork, +} from "@dokploy/server/services/network"; import type { InferResultType } from "@dokploy/server/types/with"; import boxen from "boxen"; import { writeDomainsToCompose, writeDomainsToComposeRemote, } from "../docker/domain"; +import { ensureTraefikConnectedToNetwork } from "../docker/network-utils"; import { encodeBase64, getEnviromentVariablesObject, prepareEnvironmentVariables, } from "../docker/utils"; -import { execAsync, execAsyncRemote } from "../process/execAsync"; +import { execAsyncRemote } from "../process/execAsync"; import { spawnAsync } from "../process/spawnAsync"; export type ComposeNested = InferResultType< @@ -34,9 +39,20 @@ export const buildCompose = async (compose: ComposeNested, logPath: string) => { createEnvFile(compose); if (compose.isolatedDeployment) { - await execAsync( - `docker network inspect ${compose.appName} >/dev/null 2>&1 || docker network create ${composeType === "stack" ? "--driver overlay" : ""} --attachable ${compose.appName}`, - ); + const network = await findOrCreateIsolatedNetwork({ + organizationId: compose.environment.project.organizationId, + projectId: compose.environment.projectId, + appName: compose.appName, + serverId: compose.serverId, + }); + + if (!compose.customNetworkIds?.includes(network.networkId)) { + await assignNetworkToResource( + network.networkId, + compose.composeId, + "compose", + ).catch(() => {}); + } } const logContent = ` @@ -82,9 +98,12 @@ export const buildCompose = async (compose: ComposeNested, logPath: string) => { ); if (compose.isolatedDeployment) { - await execAsync( - `docker network connect ${compose.appName} $(docker ps --filter "name=dokploy-traefik" -q) >/dev/null 2>&1`, - ).catch(() => {}); + await ensureTraefikConnectedToNetwork( + compose.appName, + compose.serverId, + ).catch((error) => { + console.warn(`Could not connect Traefik to ${compose.appName}:`, error); + }); } writeStream.write("Docker Compose Deployed: ✅"); diff --git a/packages/server/src/utils/builders/index.ts b/packages/server/src/utils/builders/index.ts index 52f8a881ef..6f6b0d68da 100644 --- a/packages/server/src/utils/builders/index.ts +++ b/packages/server/src/utils/builders/index.ts @@ -143,7 +143,7 @@ export const mechanizeDockerContainer = async ( UpdateConfig, Networks, StopGracePeriod, - } = generateConfigContainer(application); + } = await generateConfigContainer(application); const bindsMount = generateBindMounts(mounts); const filesMount = generateFileMounts(appName, application); diff --git a/packages/server/src/utils/databases/mariadb.ts b/packages/server/src/utils/databases/mariadb.ts index 196a394bce..4fd6bc679d 100644 --- a/packages/server/src/utils/databases/mariadb.ts +++ b/packages/server/src/utils/databases/mariadb.ts @@ -46,7 +46,7 @@ export const buildMariadb = async (mariadb: MariadbNested) => { UpdateConfig, Networks, StopGracePeriod, - } = generateConfigContainer(mariadb); + } = await generateConfigContainer(mariadb); const resources = calculateResources({ memoryLimit, memoryReservation, diff --git a/packages/server/src/utils/databases/mongo.ts b/packages/server/src/utils/databases/mongo.ts index 000049cf8d..23bddf464e 100644 --- a/packages/server/src/utils/databases/mongo.ts +++ b/packages/server/src/utils/databases/mongo.ts @@ -92,7 +92,7 @@ ${command ?? "wait $MONGOD_PID"}`; UpdateConfig, Networks, StopGracePeriod, - } = generateConfigContainer(mongo); + } = await generateConfigContainer(mongo); const resources = calculateResources({ memoryLimit, diff --git a/packages/server/src/utils/databases/mysql.ts b/packages/server/src/utils/databases/mysql.ts index 2956b52e56..6e5152ba43 100644 --- a/packages/server/src/utils/databases/mysql.ts +++ b/packages/server/src/utils/databases/mysql.ts @@ -52,7 +52,7 @@ export const buildMysql = async (mysql: MysqlNested) => { UpdateConfig, Networks, StopGracePeriod, - } = generateConfigContainer(mysql); + } = await generateConfigContainer(mysql); const resources = calculateResources({ memoryLimit, memoryReservation, diff --git a/packages/server/src/utils/databases/postgres.ts b/packages/server/src/utils/databases/postgres.ts index 1b0d62c0a5..e2c0035fbf 100644 --- a/packages/server/src/utils/databases/postgres.ts +++ b/packages/server/src/utils/databases/postgres.ts @@ -45,7 +45,7 @@ export const buildPostgres = async (postgres: PostgresNested) => { UpdateConfig, Networks, StopGracePeriod, - } = generateConfigContainer(postgres); + } = await generateConfigContainer(postgres); const resources = calculateResources({ memoryLimit, memoryReservation, @@ -116,8 +116,7 @@ export const buildPostgres = async (postgres: PostgresNested) => { ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1, }, }); - } catch (error) { - console.log("error", error); + } catch { await docker.createService(settings); } }; diff --git a/packages/server/src/utils/databases/redis.ts b/packages/server/src/utils/databases/redis.ts index 98c3ba62a5..40fde30c9d 100644 --- a/packages/server/src/utils/databases/redis.ts +++ b/packages/server/src/utils/databases/redis.ts @@ -43,7 +43,7 @@ export const buildRedis = async (redis: RedisNested) => { UpdateConfig, Networks, StopGracePeriod, - } = generateConfigContainer(redis); + } = await generateConfigContainer(redis); const resources = calculateResources({ memoryLimit, memoryReservation, From a8774207bda4989c060c194319868e6be04eb23f Mon Sep 17 00:00:00 2001 From: Antoine Date: Fri, 10 Oct 2025 14:39:29 +0200 Subject: [PATCH 06/51] feat(traefik): auto-generate Docker service labels from domains --- apps/dokploy/server/api/routers/domain.ts | 139 ++++++--------- packages/server/src/utils/docker/utils.ts | 208 ++++++++++++++++++++-- 2 files changed, 247 insertions(+), 100 deletions(-) diff --git a/apps/dokploy/server/api/routers/domain.ts b/apps/dokploy/server/api/routers/domain.ts index 1f6264351f..a9ee028a0b 100644 --- a/apps/dokploy/server/api/routers/domain.ts +++ b/apps/dokploy/server/api/routers/domain.ts @@ -10,6 +10,7 @@ import { findServerById, generateTraefikMeDomain, manageDomain, + mechanizeDockerContainer, removeDomain, removeDomainById, updateDomainById, @@ -26,6 +27,53 @@ import { apiUpdateDomain, } from "@/server/db/schema"; +type AuthorizationContext = { + activeOrganizationId: string; +}; + +const validateDomainAccess = async ( + domainId: string, + ctx: AuthorizationContext, +): Promise => { + const domain = await findDomainById(domainId); + + if (domain.applicationId) { + const application = await findApplicationById(domain.applicationId); + if ( + application.environment.project.organizationId !== + ctx.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this application", + }); + } + } else if (domain.composeId) { + const compose = await findComposeById(domain.composeId); + if ( + compose.environment.project.organizationId !== ctx.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this compose", + }); + } + } else if (domain.previewDeploymentId) { + const previewDeployment = await findPreviewDeploymentById( + domain.previewDeploymentId, + ); + if ( + previewDeployment.application.environment.project.organizationId !== + ctx.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this preview deployment", + }); + } + } +}; + export const domainRouter = createTRPCRouter({ create: protectedProcedure .input(apiCreateDomain) @@ -122,46 +170,11 @@ export const domainRouter = createTRPCRouter({ update: protectedProcedure .input(apiUpdateDomain) .mutation(async ({ input, ctx }) => { - const currentDomain = await findDomainById(input.domainId); + await validateDomainAccess(input.domainId, ctx.session); - if (currentDomain.applicationId) { - const newApp = await findApplicationById(currentDomain.applicationId); - if ( - newApp.environment.project.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this application", - }); - } - } else if (currentDomain.composeId) { - const newCompose = await findComposeById(currentDomain.composeId); - if ( - newCompose.environment.project.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this compose", - }); - } - } else if (currentDomain.previewDeploymentId) { - const newPreviewDeployment = await findPreviewDeploymentById( - currentDomain.previewDeploymentId, - ); - if ( - newPreviewDeployment.application.environment.project - .organizationId !== ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this preview deployment", - }); - } - } const result = await updateDomainById(input.domainId, input); const domain = await findDomainById(input.domainId); + if (domain.applicationId) { const application = await findApplicationById(domain.applicationId); await manageDomain(application, domain); @@ -175,67 +188,25 @@ export const domainRouter = createTRPCRouter({ application.appName = previewDeployment.appName; await manageDomain(application, domain); } + return result; }), one: protectedProcedure.input(apiFindDomain).query(async ({ input, ctx }) => { - const domain = await findDomainById(input.domainId); - if (domain.applicationId) { - const application = await findApplicationById(domain.applicationId); - if ( - application.environment.project.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this application", - }); - } - } else if (domain.composeId) { - const compose = await findComposeById(domain.composeId); - if ( - compose.environment.project.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this compose", - }); - } - } + await validateDomainAccess(input.domainId, ctx.session); return await findDomainById(input.domainId); }), delete: protectedProcedure .input(apiFindDomain) .mutation(async ({ input, ctx }) => { + await validateDomainAccess(input.domainId, ctx.session); + const domain = await findDomainById(input.domainId); - if (domain.applicationId) { - const application = await findApplicationById(domain.applicationId); - if ( - application.environment.project.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this application", - }); - } - } else if (domain.composeId) { - const compose = await findComposeById(domain.composeId); - if ( - compose.environment.project.organizationId !== - ctx.session.activeOrganizationId - ) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this compose", - }); - } - } const result = await removeDomainById(input.domainId); if (domain.applicationId) { const application = await findApplicationById(domain.applicationId); await removeDomain(application, domain.uniqueConfigKey); + await mechanizeDockerContainer(application); } return result; diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index 55a38bd966..45d2b195f6 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -2,9 +2,12 @@ import fs from "node:fs"; import path from "node:path"; import type { Readable } from "node:stream"; import { docker, paths } from "@dokploy/server/constants"; +import { db } from "@dokploy/server/db"; +import { networks } from "@dokploy/server/db/schema"; import type { Compose } from "@dokploy/server/services/compose"; import type { ContainerInfo, ResourceRequirements } from "dockerode"; import { parse } from "dotenv"; +import { inArray } from "drizzle-orm"; import type { ApplicationNested } from "../builders"; import type { MariadbNested } from "../databases/mariadb"; import type { MongoNested } from "../databases/mongo"; @@ -380,7 +383,166 @@ export const calculateResources = ({ }; }; -export const generateConfigContainer = ( +const resolveTraefikNetwork = async ( + customNetworkIds: string[] | null | undefined, +): Promise => { + if (!customNetworkIds || customNetworkIds.length === 0) { + return "dokploy-network"; + } + + const customNetworks = await db.query.networks.findMany({ + where: inArray(networks.networkId, customNetworkIds), + }); + + return customNetworks[0]?.networkName ?? "dokploy-network"; +}; + +const buildRouterRule = (host: string, domainPath?: string | null): string => { + const pathRule = + domainPath && domainPath !== "/" ? ` && PathPrefix(\`${domainPath}\`)` : ""; + return `Host(\`${host}\`)${pathRule}`; +}; + +const collectMiddlewares = ( + appName: string, + uniqueConfigKey: string, + domainPath: string | null | undefined, + stripPath: boolean | null, + internalPath: string | null | undefined, + includeHttpsRedirect: boolean, +): string[] => { + const middlewares: string[] = []; + + if (includeHttpsRedirect) { + middlewares.push("redirect-to-https@file"); + } + + if (stripPath && domainPath && domainPath !== "/") { + const middlewareName = `stripprefix-${appName}-${uniqueConfigKey}`; + middlewares.push(middlewareName); + } + + if (internalPath && internalPath !== "/" && internalPath.startsWith("/")) { + const middlewareName = `addprefix-${appName}-${uniqueConfigKey}`; + middlewares.push(middlewareName); + } + + return middlewares; +}; + +const generateDomainLabels = async ( + applicationId: string, + appName: string, + customNetworkIds: string[] | null | undefined, +): Promise> => { + const application = await db.query.applications.findFirst({ + where: (applications, { eq }) => + eq(applications.applicationId, applicationId), + with: { + domains: true, + }, + }); + + if (!application?.domains || application.domains.length === 0) { + return {}; + } + + const labels: Record = { + "traefik.enable": "true", + "traefik.docker.network": await resolveTraefikNetwork(customNetworkIds), + }; + + for (const domain of application.domains) { + const { + host, + port, + https, + uniqueConfigKey, + certificateType, + path: domainPath, + customCertResolver, + stripPath, + internalPath, + } = domain; + + const routerName = `${appName}-${uniqueConfigKey}`; + const serviceName = `${appName}-service-${uniqueConfigKey}`; + const webRouterName = `${routerName}-web`; + const routerRule = buildRouterRule(host, domainPath); + + labels[`traefik.http.routers.${webRouterName}.rule`] = routerRule; + labels[`traefik.http.routers.${webRouterName}.entrypoints`] = "web"; + labels[`traefik.http.routers.${webRouterName}.service`] = serviceName; + labels[ + `traefik.http.services.${serviceName}.loadbalancer.server.port` + ] = port.toString(); + + const webMiddlewares = collectMiddlewares( + appName, + uniqueConfigKey, + domainPath, + stripPath, + internalPath, + https ?? false, + ); + + if (webMiddlewares.length > 0) { + labels[`traefik.http.routers.${webRouterName}.middlewares`] = + webMiddlewares.join(","); + } + + if (stripPath && domainPath && domainPath !== "/") { + const middlewareName = `stripprefix-${appName}-${uniqueConfigKey}`; + labels[ + `traefik.http.middlewares.${middlewareName}.stripprefix.prefixes` + ] = domainPath; + } + + if (internalPath && internalPath !== "/" && internalPath.startsWith("/")) { + const middlewareName = `addprefix-${appName}-${uniqueConfigKey}`; + labels[`traefik.http.middlewares.${middlewareName}.addprefix.prefix`] = + internalPath; + } + + if (https) { + const websecureRouterName = `${routerName}-websecure`; + + labels[`traefik.http.routers.${websecureRouterName}.rule`] = routerRule; + labels[`traefik.http.routers.${websecureRouterName}.entrypoints`] = + "websecure"; + labels[`traefik.http.routers.${websecureRouterName}.service`] = + serviceName; + + const websecureMiddlewares = collectMiddlewares( + appName, + uniqueConfigKey, + domainPath, + stripPath, + internalPath, + false, + ); + + if (websecureMiddlewares.length > 0) { + labels[`traefik.http.routers.${websecureRouterName}.middlewares`] = + websecureMiddlewares.join(","); + } + + if (certificateType === "letsencrypt") { + labels[ + `traefik.http.routers.${websecureRouterName}.tls.certresolver` + ] = "letsencrypt"; + } else if (certificateType === "custom" && customCertResolver) { + labels[ + `traefik.http.routers.${websecureRouterName}.tls.certresolver` + ] = customCertResolver; + } + } + } + + return labels; +}; + +export const generateConfigContainer = async ( application: Partial, ) => { const { @@ -394,7 +556,10 @@ export const generateConfigContainer = ( replicas, mounts, networkSwarm, + customNetworkIds, stopGracePeriodSwarm, + applicationId, + appName, } = application; const sanitizedStopGracePeriodSwarm = @@ -404,6 +569,29 @@ export const generateConfigContainer = ( const haveMounts = mounts && mounts.length > 0; + let networksToUse: Array<{ Target?: string }>; + + if (networkSwarm) { + networksToUse = networkSwarm; + } else if (customNetworkIds && customNetworkIds.length > 0) { + const customNetworks = await db.query.networks.findMany({ + where: inArray(networks.networkId, customNetworkIds), + }); + networksToUse = customNetworks.map((net) => ({ Target: net.networkName })); + } else { + networksToUse = [{ Target: "dokploy-network" }]; + } + + const generatedLabels = + applicationId && appName + ? await generateDomainLabels(applicationId, appName, customNetworkIds) + : {}; + + const finalLabels = { + ...labelsSwarm, + ...generatedLabels, + }; + return { ...(healthCheckSwarm && { HealthCheck: healthCheckSwarm, @@ -418,20 +606,18 @@ export const generateConfigContainer = ( Placement: placementSwarm, } : { - // if app have mounts keep manager as constraint Placement: { Constraints: haveMounts ? ["node.role==manager"] : [], }, }), - ...(labelsSwarm && { - Labels: labelsSwarm, + ...(Object.keys(finalLabels).length > 0 && { + Labels: finalLabels, }), ...(modeSwarm ? { Mode: modeSwarm, } : { - // use replicas value if no modeSwarm provided Mode: { Replicated: { Replicas: replicas, @@ -444,7 +630,6 @@ export const generateConfigContainer = ( ...(updateConfigSwarm ? { UpdateConfig: updateConfigSwarm } : { - // default config if no updateConfigSwarm provided UpdateConfig: { Parallelism: 1, Order: "start-first", @@ -454,13 +639,7 @@ export const generateConfigContainer = ( sanitizedStopGracePeriodSwarm !== undefined && { StopGracePeriod: sanitizedStopGracePeriodSwarm, }), - ...(networkSwarm - ? { - Networks: networkSwarm, - } - : { - Networks: [{ Target: "dokploy-network" }], - }), + Networks: networksToUse, }; }; @@ -581,14 +760,11 @@ export const getComposeContainer = async ( ) => { try { const { appName, composeType, serverId } = compose; - // 1. Determine the correct labels based on composeType const labels: string[] = []; if (composeType === "stack") { - // Labels for Docker Swarm stack services labels.push(`com.docker.stack.namespace=${appName}`); labels.push(`com.docker.swarm.service.name=${appName}_${serviceName}`); } else { - // Labels for Docker Compose projects (default) labels.push(`com.docker.compose.project=${appName}`); labels.push(`com.docker.compose.service=${serviceName}`); } From 3419ed2a3b7246e6ba891e9efa7915f8fb208e55 Mon Sep 17 00:00:00 2001 From: Antoine Date: Fri, 10 Oct 2025 14:48:19 +0200 Subject: [PATCH 07/51] feat(services): integrate network management in resource services --- apps/dokploy/server/api/routers/compose.ts | 6 ++-- apps/dokploy/server/api/routers/mariadb.ts | 2 +- apps/dokploy/server/api/routers/mongo.ts | 2 +- packages/server/src/services/application.ts | 1 + packages/server/src/services/domain.ts | 36 +++++++++++++++++++++ packages/server/src/services/rollbacks.ts | 2 +- 6 files changed, 42 insertions(+), 7 deletions(-) diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index 512ea9718c..14ba4eb174 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -17,6 +17,7 @@ import { findProjectById, findServerById, findUserById, + generateFullComposePreview, getComposeContainer, IS_CLOUD, loadServices, @@ -344,10 +345,7 @@ export const composeRouter = createTRPCRouter({ message: "You are not authorized to randomize this compose", }); } - return await randomizeIsolatedDeploymentComposeFile( - input.composeId, - input.suffix, - ); + return await generateFullComposePreview(input.composeId, input.suffix); }), getConvertedCompose: protectedProcedure .input(apiFindCompose) diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts index dc811e0ca6..18950b7a3f 100644 --- a/apps/dokploy/server/api/routers/mariadb.ts +++ b/apps/dokploy/server/api/routers/mariadb.ts @@ -5,8 +5,8 @@ import { createMount, deployMariadb, findBackupsByDbId, - findMariadbById, findEnvironmentById, + findMariadbById, findProjectById, IS_CLOUD, rebuildDatabase, diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts index 1f054a1c9b..51b830fc86 100644 --- a/apps/dokploy/server/api/routers/mongo.ts +++ b/apps/dokploy/server/api/routers/mongo.ts @@ -5,8 +5,8 @@ import { createMount, deployMongo, findBackupsByDbId, - findMongoById, findEnvironmentById, + findMongoById, findProjectById, IS_CLOUD, rebuildDatabase, diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts index e9cde27c59..2c223565bb 100644 --- a/packages/server/src/services/application.ts +++ b/packages/server/src/services/application.ts @@ -289,6 +289,7 @@ export const rebuildApplication = async ({ } else if (application.sourceType === "drop") { await buildApplication(application, deployment.logPath); } + await updateDeploymentStatus(deployment.deploymentId, "done"); await updateApplicationStatus(applicationId, "done"); } catch (error) { diff --git a/packages/server/src/services/domain.ts b/packages/server/src/services/domain.ts index f81fffb965..3c63ca4854 100644 --- a/packages/server/src/services/domain.ts +++ b/packages/server/src/services/domain.ts @@ -9,10 +9,27 @@ import { type apiCreateDomain, domains } from "../db/schema"; import { findUserById } from "./admin"; import { findApplicationById } from "./application"; import { detectCDNProvider } from "./cdn"; +import { connectTraefikToResourceNetworks } from "./network"; import { findServerById } from "./server"; export type Domain = typeof domains.$inferSelect; +const connectTraefikToResource = async ( + resourceId: string, + resourceType: "application" | "compose", + serverId: string | null | undefined, +): Promise => { + try { + await connectTraefikToResourceNetworks(resourceId, resourceType, serverId); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + console.warn( + `Failed to connect Traefik to ${resourceType} networks: ${errorMessage}`, + ); + } +}; + export const createDomain = async (input: typeof apiCreateDomain._type) => { const result = await db.transaction(async (tx) => { const domain = await tx @@ -33,6 +50,25 @@ export const createDomain = async (input: typeof apiCreateDomain._type) => { if (domain.applicationId) { const application = await findApplicationById(domain.applicationId); await manageDomain(application, domain); + await connectTraefikToResource( + domain.applicationId, + "application", + application.serverId, + ); + } + + if (domain.composeId) { + const compose = await db.query.compose.findFirst({ + where: (compose, { eq }) => eq(compose.composeId, domain.composeId!), + }); + + if (compose) { + await connectTraefikToResource( + domain.composeId, + "compose", + compose.serverId, + ); + } } return domain; diff --git a/packages/server/src/services/rollbacks.ts b/packages/server/src/services/rollbacks.ts index 16877e7d95..54790375c4 100644 --- a/packages/server/src/services/rollbacks.ts +++ b/packages/server/src/services/rollbacks.ts @@ -237,7 +237,7 @@ const rollbackApplication = async ( RollbackConfig, UpdateConfig, Networks, - } = generateConfigContainer(fullContext as ApplicationNested); + } = await generateConfigContainer(fullContext as ApplicationNested); const bindsMount = generateBindMounts(mounts); const envVariables = prepareEnvironmentVariables( From 74ba1ffbd7639d0534977ceaab1cd8a16f6f288f Mon Sep 17 00:00:00 2001 From: Antoine Date: Fri, 10 Oct 2025 14:54:41 +0200 Subject: [PATCH 08/51] feat(utils): add network support to backups and collision detection --- packages/server/src/utils/backups/mariadb.ts | 2 +- packages/server/src/utils/backups/mongo.ts | 2 +- packages/server/src/utils/backups/mysql.ts | 2 +- packages/server/src/utils/backups/postgres.ts | 2 +- packages/server/src/utils/docker/collision.ts | 56 ++++++++++- .../utils/docker/collision/root-network.ts | 65 +++++++++++++ packages/server/src/utils/docker/domain.ts | 97 +++++++++++++++---- 7 files changed, 204 insertions(+), 22 deletions(-) diff --git a/packages/server/src/utils/backups/mariadb.ts b/packages/server/src/utils/backups/mariadb.ts index 2353821e5a..56cb1a9aaf 100644 --- a/packages/server/src/utils/backups/mariadb.ts +++ b/packages/server/src/utils/backups/mariadb.ts @@ -3,8 +3,8 @@ import { createDeploymentBackup, updateDeploymentStatus, } from "@dokploy/server/services/deployment"; -import type { Mariadb } from "@dokploy/server/services/mariadb"; import { findEnvironmentById } from "@dokploy/server/services/environment"; +import type { Mariadb } from "@dokploy/server/services/mariadb"; import { findProjectById } from "@dokploy/server/services/project"; import { sendDatabaseBackupNotifications } from "../notifications/database-backup"; import { execAsync, execAsyncRemote } from "../process/execAsync"; diff --git a/packages/server/src/utils/backups/mongo.ts b/packages/server/src/utils/backups/mongo.ts index 429de7d4da..2071478a0b 100644 --- a/packages/server/src/utils/backups/mongo.ts +++ b/packages/server/src/utils/backups/mongo.ts @@ -3,8 +3,8 @@ import { createDeploymentBackup, updateDeploymentStatus, } from "@dokploy/server/services/deployment"; -import type { Mongo } from "@dokploy/server/services/mongo"; import { findEnvironmentById } from "@dokploy/server/services/environment"; +import type { Mongo } from "@dokploy/server/services/mongo"; import { findProjectById } from "@dokploy/server/services/project"; import { sendDatabaseBackupNotifications } from "../notifications/database-backup"; import { execAsync, execAsyncRemote } from "../process/execAsync"; diff --git a/packages/server/src/utils/backups/mysql.ts b/packages/server/src/utils/backups/mysql.ts index 90919f24cc..d131090fa3 100644 --- a/packages/server/src/utils/backups/mysql.ts +++ b/packages/server/src/utils/backups/mysql.ts @@ -3,8 +3,8 @@ import { createDeploymentBackup, updateDeploymentStatus, } from "@dokploy/server/services/deployment"; -import type { MySql } from "@dokploy/server/services/mysql"; import { findEnvironmentById } from "@dokploy/server/services/environment"; +import type { MySql } from "@dokploy/server/services/mysql"; import { findProjectById } from "@dokploy/server/services/project"; import { sendDatabaseBackupNotifications } from "../notifications/database-backup"; import { execAsync, execAsyncRemote } from "../process/execAsync"; diff --git a/packages/server/src/utils/backups/postgres.ts b/packages/server/src/utils/backups/postgres.ts index 9aa5d8f5f6..9241f2103a 100644 --- a/packages/server/src/utils/backups/postgres.ts +++ b/packages/server/src/utils/backups/postgres.ts @@ -3,8 +3,8 @@ import { createDeploymentBackup, updateDeploymentStatus, } from "@dokploy/server/services/deployment"; -import type { Postgres } from "@dokploy/server/services/postgres"; import { findEnvironmentById } from "@dokploy/server/services/environment"; +import type { Postgres } from "@dokploy/server/services/postgres"; import { findProjectById } from "@dokploy/server/services/project"; import { sendDatabaseBackupNotifications } from "../notifications/database-backup"; import { execAsync, execAsyncRemote } from "../process/execAsync"; diff --git a/packages/server/src/utils/docker/collision.ts b/packages/server/src/utils/docker/collision.ts index 9752100ca7..9104e65a85 100644 --- a/packages/server/src/utils/docker/collision.ts +++ b/packages/server/src/utils/docker/collision.ts @@ -1,6 +1,9 @@ import { findComposeById } from "@dokploy/server/services/compose"; import { stringify } from "yaml"; -import { addAppNameToAllServiceNames } from "./collision/root-network"; +import { + addAppNameToAllServiceNames, + addCustomNetworksToCompose, +} from "./collision/root-network"; import { generateRandomHash } from "./compose"; import { addSuffixToAllVolumes } from "./compose/volume"; import { @@ -11,6 +14,9 @@ import { } from "./domain"; import type { ComposeSpecification } from "./types"; +// Re-export functions from collision/root-network +export { addCustomNetworksToCompose } from "./collision/root-network"; + export const addAppNameToPreventCollision = ( composeData: ComposeSpecification, appName: string, @@ -77,3 +83,51 @@ export const randomizeDeployableSpecificationFile = ( ); return newComposeFile; }; + +export const generateFullComposePreview = async ( + composeId: string, + suffix?: string, +) => { + const compose = await findComposeById(composeId); + + if (compose.serverId) { + await cloneComposeRemote(compose); + } else { + await cloneCompose(compose); + } + + let composeData: ComposeSpecification | null; + + if (compose.serverId) { + composeData = await loadDockerComposeRemote(compose); + } else { + composeData = await loadDockerCompose(compose); + } + + if (!composeData) { + throw new Error("Compose data not found"); + } + + const randomSuffix = suffix || compose.appName || generateRandomHash(); + + let newComposeFile = composeData; + + // 1. Apply isolated deployment transformations + if (compose.isolatedDeployment) { + newComposeFile = addAppNameToPreventCollision( + newComposeFile, + randomSuffix, + compose.isolatedDeploymentsVolume, + ); + } + + // 2. Add custom networks from customNetworkIds + if (compose.customNetworkIds && compose.customNetworkIds.length > 0) { + newComposeFile = await addCustomNetworksToCompose( + newComposeFile, + compose.customNetworkIds, + ); + } + + return stringify(newComposeFile); +}; diff --git a/packages/server/src/utils/docker/collision/root-network.ts b/packages/server/src/utils/docker/collision/root-network.ts index ed36d4bd64..c1028fd0fa 100644 --- a/packages/server/src/utils/docker/collision/root-network.ts +++ b/packages/server/src/utils/docker/collision/root-network.ts @@ -1,4 +1,7 @@ +import { inArray } from "drizzle-orm"; import _ from "lodash"; +import { db } from "../../../db"; +import { networks } from "../../../db/schema"; import type { ComposeSpecification, DefinitionsService } from "../types"; export const addAppNameToRootNetwork = ( @@ -60,3 +63,65 @@ export const addAppNameToAllServiceNames = ( return updatedComposeData; }; + +const addCustomNetworksToServices = ( + services: { [key: string]: DefinitionsService }, + networkNames: string[], +): { [key: string]: DefinitionsService } => { + return _.mapValues(services, (service) => { + if (!service.networks) { + service.networks = [...networkNames]; + return service; + } + + if (Array.isArray(service.networks)) { + for (const networkName of networkNames) { + if (!service.networks.includes(networkName)) { + service.networks.push(networkName); + } + } + } else { + for (const networkName of networkNames) { + if (!service.networks[networkName]) { + service.networks[networkName] = {}; + } + } + } + + return service; + }); +}; + +export const addCustomNetworksToCompose = async ( + composeData: ComposeSpecification, + customNetworkIds: string[], +): Promise => { + const updatedComposeData = { ...composeData }; + + if (!updatedComposeData.networks) { + updatedComposeData.networks = {}; + } + + // Fetch network details from DB + const customNetworks = await db.query.networks.findMany({ + where: inArray(networks.networkId, customNetworkIds), + }); + + // Add each custom network to compose + for (const network of customNetworks) { + updatedComposeData.networks[network.networkName] = { + name: network.networkName, + external: true, + }; + } + + // Add custom networks to all services + if (updatedComposeData.services) { + updatedComposeData.services = addCustomNetworksToServices( + updatedComposeData.services, + customNetworks.map((n) => n.networkName), + ); + } + + return updatedComposeData; +}; diff --git a/packages/server/src/utils/docker/domain.ts b/packages/server/src/utils/docker/domain.ts index 7a9521d1d4..95f8f493a5 100644 --- a/packages/server/src/utils/docker/domain.ts +++ b/packages/server/src/utils/docker/domain.ts @@ -30,7 +30,10 @@ import { createComposeFileRaw, createComposeFileRawRemote, } from "../providers/raw"; -import { randomizeDeployableSpecificationFile } from "./collision"; +import { + addCustomNetworksToCompose, + randomizeDeployableSpecificationFile, +} from "./collision"; import { randomizeSpecificationFile } from "./compose"; import type { ComposeSpecification, @@ -135,7 +138,11 @@ export const writeDomainsToCompose = async ( compose: Compose, domains: Domain[], ) => { - if (!domains.length) { + // Skip only if no domains AND no custom networks + if ( + !domains.length && + (!compose.customNetworkIds || compose.customNetworkIds.length === 0) + ) { return; } const composeConverted = await addDomainToCompose(compose, domains); @@ -154,7 +161,11 @@ export const writeDomainsToComposeRemote = async ( domains: Domain[], logPath: string, ) => { - if (!domains.length) { + // Skip only if no domains AND no custom networks + if ( + !domains.length && + (!compose.customNetworkIds || compose.customNetworkIds.length === 0) + ) { return ""; } @@ -195,7 +206,15 @@ export const addDomainToCompose = async ( result = await loadDockerCompose(compose); } - if (!result || domains.length === 0) { + // Return null only if no result OR (no domains AND no custom networks) + if (!result) { + return null; + } + + if ( + domains.length === 0 && + (!compose.customNetworkIds || compose.customNetworkIds.length === 0) + ) { return null; } @@ -211,6 +230,11 @@ export const addDomainToCompose = async ( result = randomized; } + // Add custom networks from customNetworkIds + if (compose.customNetworkIds && compose.customNetworkIds.length > 0) { + result = await addCustomNetworksToCompose(result, compose.customNetworkIds); + } + for (const domain of domains) { const { serviceName, https } = domain; if (!serviceName) { @@ -250,21 +274,57 @@ export const addDomainToCompose = async ( labels.unshift("traefik.enable=true"); } labels.unshift(...httpLabels); - if (!compose.isolatedDeployment) { - if (compose.composeType === "docker-compose") { - if (!labels.includes("traefik.docker.network=dokploy-network")) { - labels.unshift("traefik.docker.network=dokploy-network"); - } - } else { - // Stack Case - if (!labels.includes("traefik.swarm.network=dokploy-network")) { - labels.unshift("traefik.swarm.network=dokploy-network"); - } + + // Determine which network Traefik should use to reach this service + let traefikNetwork = "dokploy-network"; // Default + + // If isolatedDeployment, use the isolated network (appName) + if (compose.isolatedDeployment) { + traefikNetwork = compose.appName; + } + // If custom networks exist in the compose file, use one of them + // (Traefik will be connected to all custom networks, so any will work) + else if (result.networks && Object.keys(result.networks).length > 0) { + // Find the first non-dokploy-network network + const networkNames = Object.keys(result.networks); + const customNetwork = networkNames.find( + (name) => name !== "dokploy-network", + ); + if (customNetwork) { + traefikNetwork = customNetwork; + } + } + + // Add the appropriate network label for Traefik + if (compose.composeType === "docker-compose") { + const networkLabel = `traefik.docker.network=${traefikNetwork}`; + if ( + !labels.some( + (l) => + typeof l === "string" && l.startsWith("traefik.docker.network="), + ) + ) { + labels.unshift(networkLabel); + } + } else { + // Stack Case (Swarm) + const networkLabel = `traefik.swarm.network=${traefikNetwork}`; + if ( + !labels.some( + (l) => + typeof l === "string" && l.startsWith("traefik.swarm.network="), + ) + ) { + labels.unshift(networkLabel); } } } - if (!compose.isolatedDeployment) { + // Only add dokploy-network if no custom networks are defined + if ( + !compose.isolatedDeployment && + (!compose.customNetworkIds || compose.customNetworkIds.length === 0) + ) { // Add the dokploy-network to the service result.services[serviceName].networks = addDokployNetworkToService( result.services[serviceName].networks, @@ -272,8 +332,11 @@ export const addDomainToCompose = async ( } } - // Add dokploy-network to the root of the compose file - if (!compose.isolatedDeployment) { + // Add dokploy-network to the root of the compose file (only if no custom networks) + if ( + !compose.isolatedDeployment && + (!compose.customNetworkIds || compose.customNetworkIds.length === 0) + ) { result.networks = addDokployNetworkToRoot(result.networks); } From 7c91b3516ee8cf4890ec6bac5282169295985aa7 Mon Sep 17 00:00:00 2001 From: Antoine Date: Fri, 10 Oct 2025 14:56:00 +0200 Subject: [PATCH 09/51] test: update tests to add customNetworkIds --- apps/dokploy/__test__/drop/drop.test.ts | 1 + apps/dokploy/__test__/server/mechanizeDockerContainer.test.ts | 3 +-- apps/dokploy/__test__/traefik/traefik.test.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/__test__/drop/drop.test.ts b/apps/dokploy/__test__/drop/drop.test.ts index 54a3fba4db..7c25ea184c 100644 --- a/apps/dokploy/__test__/drop/drop.test.ts +++ b/apps/dokploy/__test__/drop/drop.test.ts @@ -134,6 +134,7 @@ const baseApp: ApplicationNested = { dockerContextPath: null, rollbackActive: false, stopGracePeriodSwarm: null, + customNetworkIds: null, }; describe("unzipDrop using real zip files", () => { diff --git a/apps/dokploy/__test__/server/mechanizeDockerContainer.test.ts b/apps/dokploy/__test__/server/mechanizeDockerContainer.test.ts index 6eb5d18318..09bcbb3b13 100644 --- a/apps/dokploy/__test__/server/mechanizeDockerContainer.test.ts +++ b/apps/dokploy/__test__/server/mechanizeDockerContainer.test.ts @@ -1,7 +1,6 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; - import type { ApplicationNested } from "@dokploy/server/utils/builders"; import { mechanizeDockerContainer } from "@dokploy/server/utils/builders"; +import { beforeEach, describe, expect, it, vi } from "vitest"; type MockCreateServiceOptions = { StopGracePeriod?: number; diff --git a/apps/dokploy/__test__/traefik/traefik.test.ts b/apps/dokploy/__test__/traefik/traefik.test.ts index 88c6c3b38a..bfcb4a9148 100644 --- a/apps/dokploy/__test__/traefik/traefik.test.ts +++ b/apps/dokploy/__test__/traefik/traefik.test.ts @@ -112,6 +112,7 @@ const baseApp: ApplicationNested = { username: null, dockerContextPath: null, stopGracePeriodSwarm: null, + customNetworkIds: null, }; const baseDomain: Domain = { From 7bc272aeb2ad0009935637501424cb3b36e37431 Mon Sep 17 00:00:00 2001 From: Antoine Date: Fri, 10 Oct 2025 14:58:01 +0200 Subject: [PATCH 10/51] fix: format with biome --- apps/dokploy/server/server.ts | 2 +- packages/server/src/index.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/dokploy/server/server.ts b/apps/dokploy/server/server.ts index eaf5626951..9da8312fe4 100644 --- a/apps/dokploy/server/server.ts +++ b/apps/dokploy/server/server.ts @@ -4,11 +4,11 @@ import { createDefaultServerTraefikConfig, createDefaultTraefikConfig, IS_CLOUD, + initCancelDeployments, initCronJobs, initializeNetwork, initSchedules, initVolumeBackupsCronJobs, - initCancelDeployments, sendDokployRestartNotifications, setupDirectories, } from "@dokploy/server"; diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index a0dac2e005..b712600add 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -25,6 +25,7 @@ export * from "./services/mariadb"; export * from "./services/mongo"; export * from "./services/mount"; export * from "./services/mysql"; +export * from "./services/network"; export * from "./services/notification"; export * from "./services/port"; export * from "./services/postgres"; @@ -68,7 +69,6 @@ export * from "./utils/backups/postgres"; export * from "./utils/backups/utils"; export * from "./utils/backups/web-server"; export * from "./utils/builders/compose"; -export * from "./utils/startup/cancell-deployments"; export * from "./utils/builders/docker-file"; export * from "./utils/builders/drop"; export * from "./utils/builders/heroku"; @@ -77,7 +77,6 @@ export * from "./utils/builders/nixpacks"; export * from "./utils/builders/paketo"; export * from "./utils/builders/static"; export * from "./utils/builders/utils"; - export * from "./utils/cluster/upload"; export * from "./utils/databases/rebuild"; export * from "./utils/docker/collision"; @@ -107,12 +106,12 @@ export * from "./utils/providers/docker"; export * from "./utils/providers/git"; export * from "./utils/providers/gitea"; export * from "./utils/providers/github"; -export * from "./utils/providers/github"; export * from "./utils/providers/gitlab"; export * from "./utils/providers/raw"; export * from "./utils/schedules/index"; export * from "./utils/schedules/utils"; export * from "./utils/servers/remote-docker"; +export * from "./utils/startup/cancell-deployments"; export * from "./utils/traefik/application"; export * from "./utils/traefik/domain"; export * from "./utils/traefik/file-types"; From f5fc02d4c7a5b7995ee10ebc9ce1bff67adb2060 Mon Sep 17 00:00:00 2001 From: Antoine Date: Fri, 10 Oct 2025 15:23:15 +0200 Subject: [PATCH 11/51] feat(ui): add shared network management components --- .../network/assign-network-to-resource.tsx | 199 +++++++++ .../dashboard/network/create-network.tsx | 386 ++++++++++++++++++ .../dashboard/network/delete-network.tsx | 79 ++++ .../components/dashboard/network/index.tsx | 5 + .../dashboard/network/network-list.tsx | 148 +++++++ .../dashboard/network/update-network.tsx | 198 +++++++++ 6 files changed, 1015 insertions(+) create mode 100644 apps/dokploy/components/dashboard/network/assign-network-to-resource.tsx create mode 100644 apps/dokploy/components/dashboard/network/create-network.tsx create mode 100644 apps/dokploy/components/dashboard/network/delete-network.tsx create mode 100644 apps/dokploy/components/dashboard/network/index.tsx create mode 100644 apps/dokploy/components/dashboard/network/network-list.tsx create mode 100644 apps/dokploy/components/dashboard/network/update-network.tsx diff --git a/apps/dokploy/components/dashboard/network/assign-network-to-resource.tsx b/apps/dokploy/components/dashboard/network/assign-network-to-resource.tsx new file mode 100644 index 0000000000..eb4cd4a446 --- /dev/null +++ b/apps/dokploy/components/dashboard/network/assign-network-to-resource.tsx @@ -0,0 +1,199 @@ +import { Network, Plus, X } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { api } from "@/utils/api"; + +interface Props { + resourceId: string; + resourceType: + | "application" + | "compose" + | "postgres" + | "mysql" + | "mariadb" + | "mongo" + | "redis"; + composeType?: "docker-compose" | "stack"; +} + +export const AssignNetworkToResource = ({ + resourceId, + resourceType, + composeType, +}: Props) => { + const [open, setOpen] = useState(false); + const utils = api.useUtils(); + + const { data: availableNetworks } = api.network.allForResource.useQuery({ + resourceType, + composeType, + }); + + const { data: assignedNetworks } = api.network.getResourceNetworks.useQuery({ + resourceId, + resourceType, + }); + + const { mutateAsync: assignNetwork, isLoading: isAssigning } = + api.network.assignToResource.useMutation(); + + const { mutateAsync: removeNetwork, isLoading: isRemoving } = + api.network.removeFromResource.useMutation(); + + const assignedNetworkIds = assignedNetworks?.map((n) => n.networkId) || []; + + const availableToAssign = availableNetworks?.filter( + (network) => !assignedNetworkIds.includes(network.networkId), + ); + + const handleAssign = async (networkId: string) => { + try { + await assignNetwork({ + networkId, + resourceId, + resourceType, + }); + + toast.success("Network assigned successfully"); + await utils.network.getResourceNetworks.invalidate({ + resourceId, + resourceType, + }); + setOpen(false); + } catch (error) { + const message = + error instanceof Error ? error.message : "Failed to assign network"; + toast.error(message); + } + }; + + const handleRemove = async (networkId: string) => { + try { + await removeNetwork({ + networkId, + resourceId, + resourceType, + }); + + toast.success("Network removed successfully"); + await utils.network.getResourceNetworks.invalidate({ + resourceId, + resourceType, + }); + } catch (error) { + const message = + error instanceof Error ? error.message : "Failed to remove network"; + toast.error(message); + } + }; + + return ( +
+
+
+

Custom Networks

+

+ Assign this service to custom networks for isolation +

+
+ + + + + + + + No networks found. + + + {availableToAssign?.map((network) => ( + handleAssign(network.networkId)} + className="cursor-pointer" + > + +
+
{network.name}
+
+ {network.networkName} • {network.driver} +
+
+ {network.isDefault && ( + + Default + + )} +
+ ))} +
+
+
+
+
+
+ + {assignedNetworks && assignedNetworks.length > 0 ? ( +
+ {assignedNetworks.map((network) => ( + + + {network.name} + + + ))} +
+ ) : ( +
+ + + No custom networks assigned. This service will use the default + dokploy-network. + +
+ )} + + {!availableNetworks || availableNetworks.length === 0 ? ( +
+

+ No custom networks available. Create a network first. +

+
+ ) : null} +
+ ); +}; diff --git a/apps/dokploy/components/dashboard/network/create-network.tsx b/apps/dokploy/components/dashboard/network/create-network.tsx new file mode 100644 index 0000000000..a72288157c --- /dev/null +++ b/apps/dokploy/components/dashboard/network/create-network.tsx @@ -0,0 +1,386 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { Network } from "lucide-react"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; +import { AlertBlock } from "@/components/shared/alert-block"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { Textarea } from "@/components/ui/textarea"; +import { api } from "@/utils/api"; + +const IPV4_REGEX = + /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; +const CIDR_REGEX = + /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(?:[0-9]|[1-2][0-9]|3[0-2])$/; + +const CreateNetworkSchema = z.object({ + name: z.string().min(1, { + message: "Name is required", + }), + networkName: z + .string() + .min(1, { message: "Network name is required" }) + .max(63, { message: "Network name must be 63 characters or less" }) + .regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/, { + message: + "Network name must start with alphanumeric and contain only alphanumeric, underscore, period, or hyphen", + }), + description: z.string().optional(), + driver: z.enum(["bridge", "overlay"]), + subnet: z + .string() + .regex(CIDR_REGEX, "Invalid subnet format (e.g., 172.20.0.0/16)") + .optional() + .or(z.literal("")), + gateway: z + .string() + .regex(IPV4_REGEX, "Invalid gateway IP address") + .optional() + .or(z.literal("")), + ipRange: z + .string() + .regex(CIDR_REGEX, "Invalid IP range format") + .optional() + .or(z.literal("")), + isDefault: z.boolean().default(false), + attachable: z.boolean().default(true), + internal: z.boolean().default(false), +}); + +type CreateNetwork = z.infer; + +interface Props { + serverId?: string | null; + projectId?: string | null; +} + +export const CreateNetwork = ({ serverId, projectId }: Props) => { + const [visible, setVisible] = useState(false); + const utils = api.useUtils(); + + const { mutateAsync, isLoading, error, isError } = + api.network.create.useMutation(); + + const form = useForm({ + defaultValues: { + name: "", + networkName: "", + description: "", + driver: "bridge", + subnet: "", + gateway: "", + ipRange: "", + isDefault: false, + attachable: true, + internal: false, + }, + resolver: zodResolver(CreateNetworkSchema), + }); + + const onSubmit = async (data: CreateNetwork) => { + await mutateAsync({ + ...data, + projectId: projectId || undefined, + serverId: serverId || undefined, + subnet: data.subnet || undefined, + gateway: data.gateway || undefined, + ipRange: data.ipRange || undefined, + }); + + toast.success("Network created successfully"); + await utils.network.all.invalidate(); + form.reset(); + setVisible(false); + }; + + return ( + + + + + + + Create Custom Network + + Create an isolated Docker network for your services + + + + {isError && {error?.message}} + +
+ +
+ ( + + Name + + + + + A friendly name for this network + + + + )} + /> + + ( + + Docker Network Name + + + + + The actual Docker network name (lowercase, alphanumeric, + dashes, dots, underscores) + + + + )} + /> + + ( + + Description + +