Docker Remote Build Execution#
Dokploy runs Docker build and deploy operations on either a local server or a remote server, controlled entirely by the presence of a serverId on the compose/application record. All builder utilities produce plain bash command strings; callers dispatch them via execAsync (local) or execAsyncRemote (SSH) depending on whether serverId is set.
The primary deployment entry points are deployCompose and rebuildCompose in services/compose.ts.
SSH Execution: execAsyncRemote#
execAsyncRemote(serverId, command, onData?) opens a fresh SSH connection per call using the ssh2 Client. It resolves the server record via findServerById and connects using server.ipAddress, server.port, server.username, and server.sshKey.privateKey with a 99,999 ms timeout . A missing sshKeyId throws immediately .
Authentication failures produce a structured, user-friendly error with hints about ~/.ssh/authorized_keys and key format . All failures throw an ExecError that carries command, stdout, stderr, exitCode, and serverId.
Because each call opens an independent SSH connection with no shared state, file persistence is the only mechanism for passing data between steps. The deploy lifecycle takes advantage of this: deployCompose runs three separate execAsyncRemote calls in sequence — clone → apply patches → build .
Build Command Assembly: getBuildComposeCommand#
getBuildComposeCommand in utils/builders/compose.ts assembles the full deploy bash string. The key line is :
env -i PATH="$PATH" HOME="$HOME" <env_vars> docker <command> 2>&1
env -i sanitization clears the inherited process environment before invoking Docker, preventing ambient shell variables (e.g., from the Node.js process or SSH session) from leaking into the Docker command. Only PATH and HOME are explicitly forwarded.
Why HOME must be preserved: docker stack deploy --with-registry-auth reads credentials from ~/.docker/config.json. Without HOME, Docker cannot locate the config file, and private-registry images fail to pull on Swarm nodes. This is regression-tested in build-compose-command.test.ts.
For stack composeType, env vars are also exported inline in the env -i prefix via getExportEnvCommand, which uses shell-quote to safely escape values .
The .env file is written to the remote filesystem (or local) via getCreateEnvFileCommand: content is base64-encoded and decoded inline via echo "<base64>" | base64 -d > "<path>" to avoid shell quoting issues . The env content automatically includes APP_NAME, COMPOSE_PROJECT_NAME, and DOCKER_CONFIG=/root/.docker .
Registry Authentication#
Registry login is handled by safeDockerLoginCommand in services/registry.ts. It shell-escapes all inputs using single-quote escaping (shEscape) and issues:
printf %s <escaped_password> | docker login <registry> -u <user> --password-stdin
Using --password-stdin avoids the password appearing in ps output or shell history.
Error sanitization: sanitizeRegistryError replaces the raw password with *** in any thrown error message before it reaches the caller.
Local vs. remote dispatch: createRegistry and updateRegistry call execAsyncRemote(serverId, loginCommand) when a serverId is present, and execAsync(loginCommand) for the local/cloud case . The docker login command stores credentials in /root/.docker/config.json on the target server, which subsequent docker pull / docker stack deploy --with-registry-auth operations then use.
Image upload flow: uploadImageRemoteCommand in utils/cluster/upload.ts assembles bash strings for tagging and pushing images to a registry. It calls safeDockerLoginCommand for each registry and supports three registry roles: primary (registry), build (buildRegistry), and rollback (rollbackRegistry). The tag format is <registryUrl>/<prefix>/<repoName> constructed by getRegistryTag.
Key Files#
| File | Role |
|---|---|
utils/process/execAsync.ts | execAsync, execAsyncRemote, execAsyncStream |
utils/builders/compose.ts | Bash string builder: env -i, .env file creation, docker compose/stack command |
services/compose.ts | Deployment lifecycle: clone → patch → build dispatch |
services/registry.ts | docker login via safeDockerLoginCommand; registry CRUD |
utils/cluster/upload.ts | Image tag + push command assembly for remote registries |
utils/docker/utils.ts | encodeBase64, prepareEnvironmentVariables, getEnvironmentVariablesObject |