Step-by-step guide to setting up a remote dev environment
A VPS (Virtual Private Server) is a remote computer in a data center that stays on 24/7. Hetzner is a German hosting provider with great prices. You'll rent one and use it as your always-on development machine.
Go to Hetzner Cloud Console and create a new project. Add a server:
SSH (Secure Shell) lets you control the remote server from your local terminal. Hetzner gives you an IP address after creation — use it to connect:
ssh root@<your-ip>
Running as root (the admin account) is risky — a typo can break everything. Create a regular user for daily work, with sudo for when you need admin privileges:
adduser axel
usermod -aG sudo axel
# Copy SSH key
mkdir -p /home/axel/.ssh
cp ~/.ssh/authorized_keys /home/axel/.ssh/
chown -R axel:axel /home/axel/.ssh
chmod 700 /home/axel/.ssh
chmod 600 /home/axel/.ssh/authorized_keys
Update all installed packages, then enable the firewall. UFW (Uncomplicated Firewall) blocks all incoming traffic except what you explicitly allow — here, just SSH so you can still connect:
# As root
apt update && apt upgrade -y
ufw allow OpenSSH
ufw enable
# Disable root login so only your user can SSH in
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
systemctl restart sshd
Claude Code is an AI coding assistant that runs in your terminal. It can read your files, write code, run commands, and handle git — like pair-programming with an AI. You'll install it on the VPS so it's always available.
The native installer downloads the CLI binary and sets up automatic updates:
# Native installer (recommended — auto-updates, no Node.js needed)
curl -fsSL https://claude.ai/install.sh | bash
claude --version
# Start claude and follow the login prompt
claude
# Or log in explicitly
claude /login
Supports Claude Pro/Max subscriptions, Console API keys, or enterprise providers (Bedrock, Vertex, Foundry).
tmux is a terminal multiplexer — it keeps your terminal sessions alive on the server even when you disconnect. Without it, closing your laptop would kill any running Claude session:
# Start a named session
tmux new -s claude
# Inside tmux, run claude
claude
# Detach: Ctrl+B, then D
# Reattach later:
tmux attach -t claude
Cloudflare sits between users and your server. It provides DNS (translating domain names to IP addresses), a global edge network (serving content from 300+ locations worldwide), and tunnels (secure connections between your VPS and Cloudflare without opening ports).
To use Cloudflare, you add your domain and point its nameservers to Cloudflare. This means Cloudflare handles all DNS for that domain:
cloudflared is the client that runs on your VPS and establishes an encrypted tunnel to Cloudflare's edge. This lets you expose services (like SSH) without opening firewall ports:
# Add Cloudflare GPG key and repo
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared noble main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
# Install
sudo apt-get update && sudo apt-get install -y cloudflared
cloudflared --version
cloudflared tunnel login
# Opens a URL — copy it to your browser and authorize
A tunnel is a persistent, encrypted connection from your VPS to Cloudflare. Traffic flows through it instead of directly to your server's IP:
# Create
cloudflared tunnel create my-tunnel
# Note the tunnel ID (UUID) from the output
# Config file is at ~/.cloudflared/config.yml
The config file maps hostnames to local services. Here, requests to ssh.yourdomain.com get routed to your VPS's SSH server:
# ~/.cloudflared/config.yml
tunnel: <TUNNEL-ID>
credentials-file: /home/axel/.cloudflared/<TUNNEL-ID>.json
ingress:
- hostname: ssh.yourdomain.com
service: ssh://localhost:22
- service: http_status:404
systemd manages background services on Linux. Installing cloudflared as a service means it starts automatically on boot and restarts if it crashes:
sudo cloudflared service install
sudo systemctl status cloudflared
This creates a DNS record so that ssh.yourdomain.com routes through your tunnel:
cloudflared tunnel route dns my-tunnel ssh.yourdomain.com
Cloudflare Workers are serverless functions that run on Cloudflare's edge network — your code runs in 300+ data centers worldwide, close to your users. No servers to manage, no scaling to worry about. You write code, deploy it, and Cloudflare handles the rest.
Node.js is a JavaScript runtime. Wrangler (the Workers CLI) requires it. nvm (Node Version Manager) lets you install and switch between Node versions easily:
# Install nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash
source ~/.bashrc
# Install latest LTS
nvm install --lts
Wrangler is Cloudflare's CLI tool for creating, testing, and deploying Workers:
npm install -g wrangler
Wrangler needs to know which Cloudflare account to deploy to. On a headless VPS (no browser), use API keys stored in a file you source before each command:
# Option A: Interactive (opens browser)
wrangler login
# Option B: API key (better for headless VPS)
# Create ~/.env.cloudflare:
export CLOUDFLARE_EMAIL="you@example.com"
export CLOUDFLARE_API_KEY="your-global-api-key"
# Source it before any wrangler command
source ~/.env.cloudflare
CLOUDFLARE_EMAIL + CLOUDFLARE_API_KEY for wrangler on a headless VPS.
A Worker project is just a folder with a config file (wrangler.toml) and a TypeScript entry point. Hono is a lightweight web framework that makes routing easy:
mkdir -p ~/projects/my-worker && cd ~/projects/my-worker
npm init -y
npm install hono
npm install -D wrangler @cloudflare/workers-types typescript
This config file tells wrangler your worker's name, entry point, and what domain to serve it on:
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-12-01"
routes = [
{ pattern = "app.yourdomain.com", custom_domain = true }
]
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c) => c.text('Hello from Cloudflare Workers!'));
export default app;
This uploads your code to Cloudflare's edge. Within seconds, it's live worldwide:
source ~/.env.cloudflare
npx wrangler deploy
With everything set up, here's how the pieces connect. You SSH into the VPS, use Claude Code to build things, and deploy to Cloudflare's edge — all from the terminal.
This is your daily workflow. SSH in, reattach to your tmux session where Claude is running, build something, and ship it:
# 1. SSH into VPS (through CF tunnel or direct)
ssh axel@ssh.yourdomain.com
# 2. Attach to tmux
tmux attach -t claude
# 3. Claude Code builds your worker
claude "Create a worker that does X"
# 4. Deploy
source ~/.env.cloudflare
cd ~/projects/my-worker
npm run build && npx wrangler deploy
~/projects/
my-worker/
src/
index.ts # Hono app
page.ts # HTML templates
client/ # React/Vite client code (if needed)
public/ # Vite build output (git-ignored)
wrangler.toml
package.json
vite.config.ts # If you have client-side JS
# Add to ~/.bashrc
alias cfe='source ~/.env.cloudflare'
alias deploy='npm run build && npx wrangler deploy'
alias cc='claude'
source ~/.env.cloudflare before any wrangler commandwrangler tail for live logswrangler.toml bindings