What's the easiest way to deploy on a VPS?

Devops & Infrastructure, Tips & Tricks, VPS, and What Is

What's the easiest way to deploy on a VPS?

You have a VPS, you have a working app, and now you need a repeatable way to ship code to it. The easiest path depends on how often you deploy, how much you value zero-downtime and rollbacks, and how much shell scripting you want to maintain. There are five common approaches — manual SSH/rsync, Capistrano, GitHub Actions, custom CI scripts, and a hosted deploy tool like DeployHQ. This post compares them on the metrics that actually matter in production, and recommends the one that wins for most teams.

If you are still deciding whether a VPS is the right host in the first place, start with the VPS 101 guide.

What easy means once you are past the first deploy

The first deploy is always easy. You SCP a folder, restart Nginx, and it works. The hard part is what happens next:

  • A teammate pushes a hotfix on a Friday afternoon. Can they deploy without you on the call?
  • A migration breaks production at 23:00. How fast can you roll back to the previous release?
  • Your build step pulls 400 MB of node_modules. Does it run on the VPS (and starve the live app of CPU) or on a build agent?
  • You add a second server behind a load balancer. Do both boxes get the same release at the same moment?
  • The senior dev leaves. Is the deploy process documented, or does it live in their ~/.bash_history?

Any deploy approach that ignores those five questions is easy only on day one. The rest of this article evaluates each option against them.

Option 1: Manual SSH + rsync or git pull

The classic. You SSH into the box, git pull the new code (or rsync from your laptop), restart the service, and check the logs.

What works: zero tooling. No new account, no new YAML, no new vendor. Great for a side project you deploy once a month.

Where it falls apart:

  • No atomic deploys. While rsync is mid-flight, the live app is serving half-old, half-new files. Static asset hashes mismatch, partial PHP files 500.
  • No rollback story. Roll back means git reset --hard <prev-sha> plus reversing any migration by hand. At 2 a.m., during an incident, this is where careers wobble.
  • Build artifacts on the production box. npm run build competes for CPU with the live process. We have seen 300 ms request latency spikes during deploys for this exact reason.
  • State drifts off Git. Hand-edited config files, ad-hoc chmod changes, a .env someone updated last quarter. Six months in, nobody can recreate the box.
  • No audit trail. Who deployed what, when? You have shell history if you are lucky.

Verdict: fine for a personal blog. Do not run a customer-facing product on it.

Option 2: Capistrano (or Mina, Deployer)

Ruby's old reliable, also good for PHP via Deployer. You define tasks in a Capfile, run cap production deploy, and the tool SSHes in, clones a release into a timestamped directory, symlinks current/, and restarts services.

What works:

  • Atomic releases via symlink swap. Roll back is cap production deploy:rollback — one command, no Git surgery. This is the genuine appeal.
  • Predictable file layout (releases/, shared/, current/).
  • Local config, version-controlled. The deploy logic lives in your repo.

Where it falls apart:

  • Ruby-shaped tool in a not-Ruby world. Capistrano expects Bundler on the deploy machine. Teams that aren't Ruby-native end up maintaining a Ruby toolchain just for deploys.
  • Build runs on production. Out of the box, cap deploy runs bundle install (or npm ci) on the target server. Same CPU contention problem as Option 1.
  • No queueing or concurrency control. Two engineers running cap deploy simultaneously will fight for the lock and one will lose.
  • Hooks and logs are ephemeral. Output streams to whoever ran the command; nothing is persisted unless you bolt it on.
  • Build pipelines are bring-your-own. You wire up tests, asset compilation, container builds yourself, in Capfile.

Verdict: still excellent for Rails monoliths with a small team. Painful for polyglot teams or anyone who needs a real audit trail.

Option 3: GitHub Actions (or GitLab CI) deploying via SSH

The default 2026 answer for I need automation: write a .github/workflows/deploy.yml, store an SSH key as an Actions secret, and push to main to deploy.

What works:

  • Free for public repos, and reasonable quota for private ones.
  • Pipeline lives next to the code. Tests, builds, and deploys are in the same YAML.
  • Build runs on a fresh runner, not on the VPS — so production CPU stays clean.
  • Pull-request previews are achievable with extra YAML.

Where it falls apart:

  • The deploy step is your responsibility. appleboy/ssh-action runs whatever shell you give it. You write the rsync, the symlink swap, the migration runner, the rollback. By the time a Capistrano-equivalent pipeline is wired up, the YAML is 200 lines of brittle shell.
  • No first-class atomic deploys, rollbacks, or release history. You build all of it on top of ssh and pray your runbook is current.
  • Secrets sprawl. SSH keys, server lists, environment variables — every project's Actions secrets diverge over time. A 30-project agency ends up with a different snowflake per client.
  • Pipeline ownership = DevOps task. Every framework upgrade or runner change can break deploys, and the pipeline is rarely the team's primary skill.
  • Cost compounds at scale. Heavy build pipelines on hosted Actions runners get expensive once you have CI minutes that matter.

Verdict: great for teams that are already living in GitHub Actions for tests and lints. The deploy logic itself is the weakest link — you are essentially rebuilding Capistrano in YAML.

Option 4: Custom CI scripts (Jenkins, Drone, GitLab Runners)

A self-hosted CI pipeline that runs a bash script which sshes to your VPS. Same shape as Option 3, but you also operate the runner.

Where it falls apart: all the cons of Option 3, plus the runner is now a server you have to patch, scale, and back up. Almost never the right answer for a small team. Worth it only if you are at hundreds of repos and have a platform-engineering team that already runs Jenkins for other reasons.

Verdict: skip unless you already have it.

Option 5: A hosted deployment tool (DeployHQ)

This is the category that exists because Options 1–4 keep failing on the same five questions. A hosted deploy tool connects to your Git host, runs the build on its own infrastructure, transfers only changed files to your VPS, swaps the release atomically, and gives you a one-click rollback and an audit log.

What DeployHQ gives you out of the box:

  • Git-triggered deploys from GitHub, GitLab, Bitbucket, or self-hosted Git — push main, and the deploy runs.
  • Build runs in an isolated container per deploy via build pipelines, so your VPS never burns CPU on npm ci.
  • Atomic releases with zero-downtime deployments — symlink-swap, just like Capistrano, without the Ruby toolchain.
  • One-click rollback to any previous release. Useful at 23:00.
  • SSH commands before and after deploy for migrations, cache warm-ups, queue restarts.
  • Config files managed outside the repo, so secrets don't end up in Git.
  • Multi-server deploys for load-balanced setups — same release, same moment, on every box.
  • Audit log and team permissions — who deployed what, when, and who is allowed to.

Where it isn't the answer:

  • You are deploying once a quarter to a hobby project — Option 1 is genuinely fine.
  • You have a PaaS-shaped app and you actually want serverless. Vercel, Netlify, Fly, and Railway are better. (DeployHQ also handles serverless deployments if you have a mixed estate.)
  • You have a hard requirement that the deploy tool run inside your VPC and never touch a third party. Self-hosted GitLab CI or Jenkins is your road.

Verdict: for a team running customer-facing applications on one or more VPSes, this is the path with the lowest ongoing cost. The setup is roughly the same effort as wiring up a serious GitHub Actions pipeline, and you stop owning the deploy logic.

Side-by-side comparison

Manual SSH Capistrano GitHub Actions Custom CI DeployHQ
Atomic releases No Yes DIY DIY Yes
One-click rollback No Yes DIY DIY Yes
Build off-server No No Yes Yes Yes
Audit log No No Partial Partial Yes
Multi-server DIY DIY DIY DIY Yes
Setup time 5 min 1–2 hrs 1–2 days 1+ week 15–30 min
Ongoing maintenance High Medium Medium-High High Low
Cost (small team) $0 $0 $0–low $$ $

Our recommendation

For most teams running production apps on a VPS — agencies, SaaS startups, internal-tools teams — DeployHQ is the easiest defensible answer. You stop maintaining bespoke shell scripts, you get atomic releases and rollbacks for free, and the deploy itself is one git push.

If you are an absolute Rails purist with a one-app, one-server setup, Capistrano is still respectable — but you're trading off the audit trail and the off-box build for ideological consistency.

If you are a hobbyist with one weekend project, manual git pull is fine. Don't over-engineer.

Everything else — the GitHub Actions YAML, the Jenkins runner, the bespoke bash deploy script — is rebuilding a deployment platform in your spare time, badly. The world has enough of those.

What setting up DeployHQ actually looks like

A first deploy on a fresh VPS, end to end, is around fifteen minutes:

  1. Connect your Git host. Authorise GitHub/GitLab/Bitbucket once.
  2. Add your server. Hostname, SSH user, key (DeployHQ shows you exactly what to add to authorized_keys), deploy path. Use a non-root user — see our setup guide for Git-based deployment on a VPS for the user/permissions setup.
  3. Pick a build pipeline if your stack needs one. npm ci && npm run build, composer install --no-dev, pnpm install && pnpm build, whatever your package.json/composer.json already runs locally.
  4. Add SSH commands for post-deploy work — migrations, php artisan octane:reload, systemctl reload nginx, etc.
  5. Push. The first deploy uploads the full release; subsequent deploys transfer only the diff.

The first time you watch a green deploy roll across three VPSes in 40 seconds without a shell prompt being involved, you stop missing the YAML.

When the VPS itself is the bottleneck

Sometimes make deploys easier is masking a different problem. If you are seeing CPU steal time, your IOPS are pinned, or your single VPS keeps OOMing during deploys, no deploy tool will fix that — you need to upgrade the host. The VPS 101 guide covers what to look for in a provider; if you are already on shared hosting and considering a move, our shared hosting vs VPS comparison is the right starting point.

Get started

Start a free DeployHQ trial — connect your repo, point it at your VPS, and your next git push is your next deploy. No credit card required.

Running multiple sites or client projects? The agency plan gives unlimited team members and project templating; full pricing is on the plans page.

Questions about migrating to a VPS or wiring up automated deploys? Email us at support@deployhq.com or ping @deployhq on X.