๐ก online.colaba.ssh โ Gradle plugin for easy deployment
Copy folders & files from your local machine (or CI runner) to a remote server over SSH, and run remote commands โ with zero config, or fully customized.
Files land in the remote ~/${project.name}/... folder.
The plugin shells out to the system OpenSSH (ssh / scp) and rsync via ProcessBuilder. A single
ControlMaster connection is reused for every copy/command in a task run. There is no bundled SSH library
(no JSch / groovy-ssh), so any key the OS ssh understands works โ ed25519, RSA, the OpenSSH key format, etc.
When both ends have rsync, copies go through it over the same connection:
- a jar folder (or any folder replaced as a whole) is mirrored, and only changed blocks travel. A rebuilt 140 MB Spring Boot fat jar shares almost all of its bytes with the previous one, so a redeploy sends kilobytes to a few MB instead of the whole jar. Files are replaced through a temp file, so an interrupted copy leaves the previous version in place instead of an empty folder;
- small files of one task (compose files, Dockerfiles, gradle files) go out in one call instead of one round trip per file.
Without rsync on either end, every copy falls back to scp, file by file. Force that per task with
rsync = false.
Whatever the transport:
- an empty local folder is never shipped: replacing the remote copy with nothing (after a
clean, or a build that produced no jar) fails the task instead, and the server keeps what it had; - a symlink travels as the file it points to, as with
scp; - a link that dies without a reset fails the task after about a minute instead of hanging the build.
Requirements:
sshandscp(OpenSSH) onPATHof the machine running the tasks (any CI runner and macOS/Linux dev box has them by default).rsyncon both ends is optional and only makes copies faster; macOSopenrsyncworks too.
In the root build.gradle.kts:
plugins {
id("online.colaba.ssh") version "2.2.1"
}
group = "online.colaba" // host is computed from group if not set explicitlyThat's it. Run a copy:
gradle scpOverride the host per task:
tasks {
scp { host = "my-domain.com" }
}The plugin looks for a private key named id_rsa โ first in the project root, then in ~/.ssh/id_rsa.
The file name is id_rsa regardless of key type; the contents may be ed25519 (recommended) or RSA.
ssh-keygen -t ed25519 -C "deploy@my-server" # modern key
ssh-copy-id -i ~/.ssh/id_ed25519.pub root@my-server # authorize it on the server
cp ~/.ssh/id_ed25519 id_rsa # the plugin reads ./id_rsa๐ Never commit
id_rsa. Gitignore it and, in CI, materialize it from a secret before the deploy step (e.g.printf '%s\n' "${{ secrets.SSH_DEPLOY_KEY }}" > id_rsa && chmod 600 id_rsa).
Host-key checking is disabled by default; set checkKnownHosts = true on a task to enable it.
Generated automatically (one per subproject where relevant):
| Task | What it copies / does |
|---|---|
ssh |
base task, every option false by default |
scp |
base copy task, options enabled by default |
ssh-<subproject> |
the subproject's backend build/libs/*.jar (or whole folder) |
ssh-docker |
all compose / Dockerfile / .dockerignore files (incl. subprojects) |
ssh-gradle |
gradle wrapper + build files into every subproject |
ssh-vault |
vault/ (config/agent/policies) + root docker-compose.infra.yml |
ssh-elastic |
whole elastic/ folder |
ssh-broker |
whole broker/ folder |
ssh-nginx |
whole nginx/ folder |
ssh-monitoring |
whole monitoring/ folder |
ssh-frontend-whole |
whole frontend/ folder |
clear-frontend |
(local) remove node_modules, .nuxt, .output, lockfiles |
Docker helpers (run on the remote host):
| Task | What it does |
|---|---|
compose |
docker compose up all services (recreate + rebuild) |
compose-<svc> |
docker compose up a single service |
rollout-<svc> |
zero-downtime rollout: scale up new instance, await healthcheck, drain old |
ps |
list containers |
prune |
remove unused docker data |
cmd |
run an arbitrary local command (linux/windows) |
Run gradle tasks to see the full list under the ssh and docker-main-${project.name} groups.
Register your own task:
register("deployApi", online.colaba.Ssh::class) {
host = "my-domain.com"
user = "root"
gradle = true
docker = true
nginx = true
directory = "distribution"
run = "cd ${project.name} && docker compose up -d --build"
}Or tweak the defaults:
ssh {
host = "my-domain.com"
directory = "copy_me_to_remote"
nginx = true
}project
โโ backend/ โ build/libs/*.jar, Dockerfile, compose.yml
โโ backend-2/ โ any number of backends
โโ frontend/ โ compose.yml, .output
โโ nginx/
โโ postgres/ โ backups/
โโ elastic/
โโ vault/
โโ gradlew, build.gradle.kts, docker-compose.yml
โโ id_rsa โ gitignored private key
The service name for every task equals ${project.name} of the respective subproject.