Database Management in Dokploy#
Dokploy manages six database types — PostgreSQL, MySQL, MariaDB, MongoDB, Redis, and LibSQL — each running as a Docker Swarm service. All six follow an identical architectural pattern: a schema definition, a service layer (CRUD + deploy), a Docker builder (Swarm service spec), and a tRPC router (API surface). The primary operations are deploy, rebuild (destructive reset), start/stop/reload (lifecycle), and volume cleanup — all of which apply uniformly across types with per-type variations only where the database requires it.
Supported Types & Key Files#
Each database type has four canonical source locations:
The shared schema index is at packages/server/src/db/schema/index.ts.
Deploy Flow#
Each deployXxx(id, onData?) function in services/ follows the same pattern :
- Set
applicationStatus = "running"in the database. - Pull the Docker image (locally via
pullImage()or remotely viaexecAsyncRemote). - Call
buildXxx(db_record)fromutils/databases/, which creates or updates the Docker Swarm service spec with env vars, ports, volume mounts, and resource limits. - Set
applicationStatus = "done"on success,"error"on failure.
Notable per-type behaviors:
- MySQL: On creation, automatically creates a named volume mount at
/var/lib/mysql. - PostgreSQL:
getMountPath()auto-selects the correctPGDATApath based on image version —/var/lib/postgresql/{version}/dockerfor v18+,/var/lib/postgresql/datafor older versions . - MongoDB: Supports optional replica set initialization — the builder generates a startup script when
replicaSetsis enabled . - Redis: Defaults to
redis-server --requirepass [password]if no custom command is provided . - LibSQL: Image is pinned to
ghcr.io/tursodatabase/libsql-server:v0.24.32; exposes HTTP (8080), gRPC (5001), and Admin (5000) ports .
All builders use dnsrr DNS mode and host-mode port publishing, and support remote server targeting via serverId.
Rebuild Architecture#
rebuildDatabase(databaseId, type) in packages/server/src/utils/databases/rebuild.ts is the single entry point for all database types. It is destructive — all data is erased.
Rebuild sequence :
- Remove service — calls
removeService(appName, serverId), which runsdocker service rm. - 6-second delay —
await new Promise(resolve => setTimeout(resolve, 6000)). This allows Docker to fully release volume references before removal is attempted. - Volume cleanup — iterates
database.mounts; for eachmount.type === "volume", runsdocker volume rm <volumeName> --forcelocally or viaexecAsyncRemotefor remote servers . - Redeploy — dispatches to the type-specific
deployXxx(id)to recreate the service with a clean state .
The DatabaseType union covers all six supported types: "libsql" | "mariadb" | "mongo" | "mysql" | "postgres" | "redis" .
Each router exposes this as a .rebuild() tRPC mutation with deployment: ["create"] permission checks — see MySQL router as a representative example. This feature was introduced in PR #1440.
Service Lifecycle#
All lifecycle operations map to Docker Swarm scaling commands in utils/docker/utils.ts:
| Operation | Local | Remote | Effect |
|---|---|---|---|
| Start | startService(appName) | startServiceRemote(serverId, appName) | docker service scale appName=1 |
| Stop | stopService(appName) | stopServiceRemote(serverId, appName) | docker service scale appName=0 |
| Remove | removeService(appName, serverId?) | same (serverId branch) | docker service rm appName |
Reload (non-destructive restart) is implemented per router as a stop → applicationStatus = "idle" → start → applicationStatus = "done" sequence, preserving all volumes — see the MySQL reload handler.
Health checks for the Dokploy-internal PostgreSQL and Redis services are implemented in utils/docker/utils.ts via checkPostgresHealth() and checkRedisHealth(), which exec pg_isready and redis-cli ping inside the running container.
Volume Cleanup Safety#
Volumes are intentionally excluded from automatic cleanup. The comment in utils/docker/utils.ts explains the policy:
"Volume cleanup should always be performed manually by the user. The reason is that during automatic cleanup, a volume may be deleted due to a stopped container, which is a dangerous situation."
This is enforced via excludedCleanupAllCommands : cleanupAll() and cleanupAllBackground() skip the volumes command. The standalone cleanupVolumes() function exists for explicit manual use only.
All cleanup commands (containers, images, builders, volumes) are wrapped by dockerSafeExec(), which polls ps aux every 10 seconds until Docker is idle before executing — preventing race conditions with concurrent deployments.
In rebuild, volume cleanup is targeted rather than global: only the mounts belonging to the specific database being rebuilt are removed .