Remote Server Setup#
Dokploy's remote server provisioning is driven by server-setup.ts. The top-level entry point is serverSetup(serverId, onData?), which creates a deployment record, streams log output to the caller, runs the full installation sequence via installRequirements(), and updates the deployment status on completion or error.
Two tRPC endpoints expose this function :
server.setup(mutation) — fire-and-forget invocationserver.setupWithLogs(subscription) — streams log lines in real time via anonDatacallback
A companion server.validate query calls serverValidate() to return pre-flight results (Docker version, RClone, Nixpacks, Buildpacks, Railpack, sudo mode, docker-group membership) without running the full setup.
How the Setup Script Works#
installRequirements opens a direct ssh2 Client connection to the server using its ipAddress, port, username, and sshKey.privateKey. It executes either a custom command stored on the server record or the generated defaultCommand(isBuildServer?) bash script.
The generated script is a single self-contained bash program that runs set -e and proceeds through numbered steps. For a standard (non-build) server these are :
- Install utilities (curl, wget, git, git-lfs, jq, openssl, unzip)
- Validate ports (80/443)
- Install RClone
- Install Docker
- Init Docker Swarm
- Create the
dokploy-networkoverlay network - Set up
/etc/dokploydirectory tree - Write Traefik config and middleware
- Launch the
dokploy-traefikcontainer - Install Nixpacks
- Install Buildpacks
- Install Railpack
- Configure permissions
Build servers skip networking, Traefik, and port checks, running only Docker + directory + builder-tool steps .
Supported OS families: Debian/Ubuntu/Raspbian, RHEL/CentOS/Fedora/AlmaLinux/Rocky/Amazon Linux, Arch/Manjaro, Alpine, SLES/openSUSE. Variants like Manjaro, Pop!_OS, LinuxMint, Zorin, and Fedora Asahi are remapped to their base OS type .
Sudo / Non-Root User Handling#
PR #4059 added first-class non-root support. At the top of the generated script, the privilege mode is auto-detected :
- Root (
EUID = 0) →SUDO_CMD="", all commands run directly. - Non-root with passwordless sudo (
sudo -n truesucceeds) →SUDO_CMD="sudo", prepended to every privileged operation. - Non-root without passwordless sudo → script exits with an error message and the configuration hint:
echo '$CURRENT_USER ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/$CURRENT_USER
$SUDO_CMD is then used consistently across all 60+ privileged operations: package manager calls, Docker installation, systemctl, Docker Swarm init, network creation, directory operations, and builder-tool installations .
Post-setup permissions (setupPermissions()): if a sudo user was detected, the script adds $CURRENT_USER to the docker group and sets ownership of /etc/dokploy to that user. Root sessions skip these steps.
Validation functions in server-validate.ts expose two shell-based checks for UI display :
validateSudoAccess()— emits"root true","sudo true", or"none false"validateDockerGroup()— checks group membership viagroups $USER
The server.validate tRPC query runs these and surfaces the results as Privilege Mode and Docker Group status rows in the Dokploy UI.
Railpack Installation#
Railpack is installed in two distinct contexts:
1. Server Provisioning (server-setup.ts)#
installRailpack() checks for an existing railpack binary; if absent, it exports RAILPACK_VERSION=0.15.4 and runs the upstream installer via $SUDO_CMD bash -c "$(curl -fsSL https://railpack.com/install.sh)". This runs as step 13 of the full server setup (step 6 for build servers).
2. Per-Build Execution (railpack.ts)#
getRailpackCommand() in packages/server/src/utils/builders/railpack.ts generates the build-time bash script. It begins by re-installing Railpack at the version pinned to the application's railpackVersion field :
export RAILPACK_VERSION=${application.railpackVersion}
bash -c "$(curl -fsSL https://railpack.com/install.sh)"
This was added by PR #3113 to guarantee the correct per-application version is active before the build runs. The script then:
- Creates an isolated Docker buildx builder (
railpack-<appName>-<nanoid>) to avoid concurrent build collisions - Runs
railpack prepareto emit a build plan JSON - Runs
docker buildx buildwith the Railpack frontend and secrets injected as--secretargs - Removes the ephemeral builder
Environment secrets are passed as Docker build secrets (not baked into the image) and a secrets-hash build-arg is used to control layer cache invalidation .
SSH Connection and Error Handling#
installRequirements uses the ssh2 Client directly (not execAsyncRemote) for server setup, because it needs to stream output before any serverId lookup helpers are available. Authentication errors are caught and presented as friendly messages with actionable hints :
- Auth failure → hints about checking
~/.ssh/authorized_keysand regenerating the key in Dokploy - Connection failure → hints about IP/port correctness, firewall rules, and SSH service status
A missing sshKeyId short-circuits immediately with "❌ No SSH Key found" before any connection attempt .
Key Files#
| File | Purpose |
|---|---|
packages/server/src/setup/server-setup.ts | Main setup orchestration and all bash script generators |
packages/server/src/setup/server-validate.ts | Pre-flight validation bash generators and serverValidate() |
packages/server/src/utils/builders/railpack.ts | Railpack build-time command generator |
apps/dokploy/server/api/routers/server.ts | tRPC router: setup, setupWithLogs, validate endpoints |