Claude Code on a VPS: Deploy and Build from Your Terminal
Step-by-step guide to connecting Claude Code to your VPS via SSH, automating deployments with a deploy script, and managing your site from the CLI.
TL;DR
Install Claude Code on your VPS, set up a GitHub SSH deploy key, write a one-line deploy script, and run deployments straight from your local terminal. No CI/CD platform needed. One SSH command builds and ships your site.
In This Guide click to collapse
The Architecture: Local CLI to Live Site
The idea is simple: skip the CI/CD platform entirely. Instead of configuring GitHub Actions, Vercel hooks, or Netlify build pipelines, I use Claude Code on my local machine to SSH into the VPS and run a deploy script. One command, and the site goes from repository to production.
| Stage | Where | What Happens |
|---|---|---|
| 1. Develop | Local machine | Edit code, test locally, push to GitHub |
| 2. Trigger | Local terminal | Claude Code runs ssh yourserver "~/deploy.sh" |
| 3. Build | VPS | git pull, npm install, npm run build |
| 4. Ship | VPS | rsync build output to web root, site is live |
This works with any VPS provider. I run mine on ScalaHosting with AlmaLinux and SPanel, but the same approach applies to DigitalOcean, Hetzner, Linode, or any server you can SSH into.
One Pipeline, Zero CI/CD Platforms
No YAML configs, no build minutes quota, no webhook debugging. Your VPS does the building and serving. Claude Code just triggers it over SSH.
Prerequisites and VPS Setup
What You Need
| Requirement | Version / Details | Purpose |
|---|---|---|
| VPS with SSH access | Any Linux distribution | Remote server to build and host |
| Node.js | 18+ (LTS recommended) | Build toolchain for Astro/Next/etc. |
| Git | 2.x+ | Pull from GitHub on the VPS |
| Claude Code CLI | Latest | Local terminal interface |
| GitHub repo | Public or private | Source of truth for your code |
I use ScalaHosting with AlmaLinux 9.5 and SPanel. The control panel gives you SSH access, file management, and SSL certificates out of the box. Any provider that gives you root or user-level SSH works.
SSH Configuration with Custom Port and Alias
Most VPS providers let you change the SSH port from the default 22. This cuts down on automated brute-force noise dramatically. Set up an alias in your local SSH config so you never have to remember the port or full hostname.
# ~/.ssh/config
Host yourserver
HostName your-server-ip
User youruser
Port 2222
IdentityFile ~/.ssh/id_ed25519
Now ssh yourserver connects you directly. No flags, no port numbers.
Quick SSH Security Wins
- Custom port: Change from 22 to something like 2222 or 6543. Stops most automated scanners.
- Key-only auth: Set
PasswordAuthentication noin/etc/ssh/sshd_config. Eliminates brute-force attacks entirely. - Disable root login: Set
PermitRootLogin no. Always SSH as a regular user and usesudowhen needed. - SSH alias: Saves keystrokes and prevents port/hostname typos.
Installing Claude Code on the VPS
Installation and Authentication
SSH into your VPS and install Claude Code globally via npm:
# SSH to your VPS
ssh yourserver
# Install Claude Code globally
npm install -g @anthropic-ai/claude-code
# Verify it installed
claude --version
# Authenticate (follow the browser flow)
claude auth login
The “command not found” Trap
If claude returns “command not found” after installation, your npm global bin directory is not in PATH. This is especially common on AlmaLinux, CentOS, and RHEL-based systems. See the fix below.
Environment Essentials
The most common post-install issue is PATH not including npm’s global bin directory. Fix it by adding the path to your shell profile:
# Find where npm installs global binaries
npm config get prefix
# Typically outputs: /home/youruser/.npm-global or /usr/local
# Add to your profile
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
# Verify
claude --version
Environment Checklist
| Check | Command | Expected Output |
|---|---|---|
| Node.js | node --version |
v18+ or v20+ |
| npm | npm --version |
8+ or 10+ |
| Git | git --version |
2.x+ |
| Claude CLI | claude --version |
Any version number |
| SSH key | ls ~/.ssh/github_deploy* |
Key files listed |
GitHub SSH Deploy Keys
Why a Deploy Key
Your VPS needs to pull from GitHub, but you should not put your personal SSH key or a full-access token on a remote server. Deploy keys solve this: they grant read-only access to a single repository.
| Method | Scope | Access Level | Revocation |
|---|---|---|---|
| Deploy Key | Single repository | Read-only (default) | Remove from repo settings |
| Personal Access Token | All repos (or selected) | Read/write (configurable) | Revoke in GitHub settings |
| Personal SSH Key | All repos you own | Full read/write | Remove from GitHub account |
The deploy key wins for VPS use. If the server is ever compromised, the attacker gets read access to one repository, not your entire GitHub account.
Setting It Up
Generate a dedicated key on the VPS, then add the public key to your GitHub repository.
# On the VPS: generate a key pair
ssh-keygen -t ed25519 -f ~/.ssh/github_deploy -C "deploy-key-yourserver"
# Display the public key (copy this)
cat ~/.ssh/github_deploy.pub
Add the public key in GitHub under Settings > Deploy Keys > Add deploy key. Leave “Allow write access” unchecked.
Now configure SSH on the VPS to use this key for GitHub:
# ~/.ssh/config (on the VPS)
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/github_deploy
IdentitiesOnly yes
Test the connection:
ssh -T git@github.com
# Should output: Hi your-github-username/your-repo-name! You've successfully authenticated...
Deploy Keys Are Single-Repo Scoped
Unlike personal SSH keys, a deploy key only works with the one repository you add it to. If you need to pull from multiple repos on the same VPS, generate a separate deploy key for each and use different Host aliases in your SSH config.
The Deploy Script
Writing deploy.sh
Create a script on the VPS that handles the full pipeline: pull, install, build, and sync. This is the single file that turns ssh yourserver "~/deploy.sh" into a complete deployment.
#!/bin/bash
set -e
PROJECT_DIR="$HOME/projects/your-repo-name"
PUBLIC_DIR="$HOME/public_html"
echo "=== Deploying site ==="
# Pull latest code
cd "$PROJECT_DIR"
git pull origin main
# Install dependencies
npm install
# Build the site
npm run build
# Sync to web root
rsync -av --delete \
--exclude='wp-*' \
--exclude='.htaccess' \
--exclude='.well-known' \
--exclude='index.php' \
dist/ "$PUBLIC_DIR/"
echo "=== Deploy complete ==="
Make it executable:
chmod +x ~/deploy.sh
What Each Step Does
| Step | Command | Purpose |
|---|---|---|
| 1 | set -e |
Exit immediately if any command fails (no silent errors) |
| 2 | git pull origin main |
Fetch latest code from GitHub via deploy key |
| 3 | npm install |
Install or update dependencies |
| 4 | npm run build |
Generate static output in dist/ |
| 5 | rsync -av --delete |
Sync build output to web root, removing stale files |
The Rsync Strategy
The rsync command does the heavy lifting. The --delete flag removes files from the web root that no longer exist in the build output. But some files must survive every deploy: your .htaccess, WordPress files (if running a hybrid setup), and SSL validation directories.
| Exclusion | Why |
|---|---|
wp-* |
Preserves WordPress core files (hybrid setups) |
.htaccess |
Keeps custom Apache rules and security headers |
.well-known |
Preserves SSL certificate validation files |
index.php |
Keeps WordPress entry point (hybrid setups) |
The –delete Flag Is Destructive
Without the --exclude flags, rsync --delete will wipe everything in the target directory that isn’t in your build output. Double-check your exclusions before running the first deploy. One missing exclude can delete your .htaccess or SSL files.
Always Test with –dry-run First
Before your first real deploy, run rsync -avn --delete --exclude='...' dist/ ~/public_html/ with the -n (dry-run) flag. It shows exactly what would be added, deleted, or changed without touching any files. Verify the output before removing the flag.
Running Deployments from Claude Code
One-Command Deploy
With your SSH alias and deploy script in place, deploying from Claude Code is a single command:
ssh yourserver "~/deploy.sh"
Claude Code can run this directly from your local terminal. You can also ask it conversationally: “deploy the site” or “run the deploy script on the server.” It executes the SSH command and streams the output back to you.
What a Deployment Looks Like
You: deploy the site to production
Claude Code: I'll run the deploy script on your VPS.
$ ssh yourserver "~/deploy.sh"
=== Deploying site ===
Already on 'main'
From github.com:your-github-username/your-repo-name
a1b2c3d..d4e5f6g main -> origin/main
Updating a1b2c3d..d4e5f6g
Fast-forward
src/pages/index.astro | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
npm install: up to date
Building site...
generating static routes
▶ src/pages/index.astro
▶ src/pages/about.astro
▶ src/pages/blog/index.astro
build complete in 4.2s
sending incremental file list
=== Deploy complete ===
Site is live. The deploy pulled 1 changed file, rebuilt, and synced to production.
Claude Code as Your DevOps Partner
Deployment is just one thing you can do remotely. Because Claude Code can run any SSH command, your VPS becomes an extension of your local terminal. Check disk space, tail logs, restart services, inspect processes, all without opening a separate SSH session.
| Task | Command | What It Shows |
|---|---|---|
| Deploy | ssh yourserver "~/deploy.sh" |
Full deploy pipeline output |
| Check disk space | ssh yourserver "df -h" |
Filesystem usage on all mounts |
| View recent logs | ssh yourserver "tail -20 ~/logs/error.log" |
Last 20 lines of error log |
| Check running processes | ssh yourserver "pm2 status" |
PM2-managed process list and status |
| Restart a service | ssh yourserver "pm2 restart app-name" |
Service restart confirmation |
| Memory usage | ssh yourserver "free -h" |
RAM usage breakdown |
Your VPS as an Extension of Your Terminal
The mental model shift: stop thinking of your VPS as a remote machine you “log into.” With Claude Code and SSH aliases, it becomes another tool in your local workflow. Ask Claude to check something on the server the same way you’d ask it to read a local file.
Security Hardening
Running deployments over SSH means your security baseline matters. A compromised VPS with a deploy key gives an attacker access to your repository (read-only, but still). Lock things down.
| Path | Permission | Purpose |
|---|---|---|
~/.ssh/github_deploy |
600 | Deploy key private key |
~/.ssh/config |
600 | SSH client configuration |
~/deploy.sh |
700 | Deploy script (owner-execute only) |
~/.claude/ |
700 | Claude CLI config and credentials |
Harden SSH on the VPS itself by editing /etc/ssh/sshd_config:
# /etc/ssh/sshd_config
Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
If you run Apache, add security headers and block sensitive files in .htaccess:
# Block sensitive files
<FilesMatch "(\.env|\.git|deploy\.sh)$">
Require all denied
</FilesMatch>
# Security headers
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "SAMEORIGIN"
Header set Referrer-Policy "strict-origin-when-cross-origin"
Never Commit Credentials
Your .env file, SSH keys, and API tokens must never appear in your Git repository. Add them to .gitignore and verify with git status before every commit.
The Security Minimum
These four items are non-negotiable for any VPS running deployments:
- SSH key-only authentication (no passwords)
- Non-default SSH port
- Read-only deploy key (not a personal SSH key)
- 600/700 permissions on all sensitive files
FAQ
Can I use this setup with any static site generator?
Yes. The deploy script runs npm run build and rsyncs the output directory. This works with Astro, Next.js (static export), Hugo, Eleventy, or any tool that produces a folder of static files. Just change the dist/ path in the rsync command to match your generator’s output directory (out/ for Next.js, public/ for Hugo, etc.).
What if the build fails on the VPS?
The set -e flag at the top of the deploy script stops execution on any error. If npm run build fails, the rsync step never runs, so your live site stays untouched. You’ll see the error output in your terminal. Fix the issue locally, push to GitHub, and re-run the deploy.
Is it safe to have Claude Code installed on a production VPS?
Claude Code on the VPS is optional for this workflow. The core pipeline only needs Git, Node.js, and rsync on the server. Claude Code is installed locally on your development machine and triggers the deploy over SSH. If you do install it on the VPS (for remote AI-assisted debugging), restrict access with proper file permissions on the ~/.claude/ directory.
How do I roll back a bad deployment?
The fastest rollback is reverting the commit on GitHub, then re-running the deploy script. Alternatively, keep the previous build output: modify the deploy script to copy dist/ to a timestamped backup folder before each rsync. If a deploy breaks something, rsync the backup back to the web root.
How does this differ from using the VPS as a full workstation?
This workflow treats the VPS as a deploy target: Claude Code runs on your laptop, the VPS just builds and ships. The opposite pattern is to make the VPS your primary dev machine, with Claude Code running in a persistent tmux session on the server itself. You SSH in from any device (laptop, phone) and pick up exactly where you left off. I cover the full setup, including tmux from source without sudo and dual GitHub identities, in Advanced Claude Code VPS Setup: The Full Workstation Pattern. The two patterns stack: the same VPS can be both your deploy target and your primary workstation.
Can I deploy from my phone?
Not directly with Claude Code, but you can pair this setup with a Telegram bot that runs on the VPS. Send a /deploy command from your phone, and the bot executes ~/deploy.sh locally on the server. I covered that approach in a separate article on automating VPS management with Telegram.
Why not just use GitHub Actions or Vercel?
Those are great tools. But for a personal site on a VPS you already pay for, adding a CI/CD platform introduces another moving part with its own configuration, build minutes, and potential failure points. The SSH approach has zero external dependencies: your code is on GitHub, your server builds it, done. No third-party service sitting between your repository and your live site.
