Hetzner Cloud Provider
Deploy preview environments to Hetzner Cloud via SSH.
How it works
- Build: SSHs into a fresh Hetzner server, clones the repo, and runs
docker build. Docker and Git are installed automatically via cloud-init on first boot. - Deploy: runs the built Docker image on the same server. Each PR gets its own server.
- Delete: deletes the Hetzner server via the Cloud API.
- List: lists all Hetzner servers with the label
previewops=true.
Prerequisites
- A Hetzner Cloud account and project.
- An SSH key pair — the public key registered in Hetzner, the private key stored in Previewops secrets.
Step 1 — Create a Hetzner API token
- Go to your Hetzner Cloud project → Security → API Tokens → Generate API Token.
- Select Read & Write permission.
- Store the token as
HETZNER_API_TOKENin Previewops secrets.
Step 2 — Register an SSH key in Hetzner
If you need a new key pair:
ssh-keygen -t ed25519 -C "previewops" -f ~/.ssh/previewops-hetzner
- Go to your project → Security → SSH Keys → Add SSH Key.
- Paste the contents of
~/.ssh/previewops-hetzner.pub. - Give it a name (e.g.
previewops) — this becomessshKeyNameinproviderConfig.
Store the private key as HETZNER_SSH_KEY in Previewops secrets:
cat ~/.ssh/previewops-hetzner
Step 3 — Store secrets in Previewops
| Secret | Value |
|---|---|
HETZNER_API_TOKEN |
API token from step 1 |
HETZNER_SSH_KEY |
PEM-encoded private key from step 2 |
Step 4 — Configure the repo
provider: hetzner
providerConfig:
sshKeyName: previewops # required — the exact name you gave the SSH key in Hetzner (step 2)
serverType: cx22 # optional (default: cx22); run `hcloud server-type list` for all options
location: nbg1 # optional (default: nbg1)
# options: nbg1 (Nuremberg), fsn1 (Falkenstein), hel1 (Helsinki),
# ash (Ashburn, VA), hil (Hillsboro, OR), sin (Singapore)
image: ubuntu-24.04 # optional (default: ubuntu-24.04)
Complete example with all options:
# .previewops.yaml
provider: hetzner
providerConfig:
sshKeyName: previewops # exact name of the SSH key registered in Hetzner (required)
serverType: cx22 # server size (default: cx22 — 2 vCPU, 4 GB RAM, ~€0.005/hr)
location: nbg1 # server location (default: nbg1)
image: ubuntu-24.04 # OS image (default: ubuntu-24.04)
# Common options — all optional
concurrency: 3 # max simultaneous active previews per repo
ttlHours: 24 # auto-delete after N hours (default: 24)
port: 3000 # optional — auto-detected from your Dockerfile EXPOSE; falls back to 8080
memory: 512Mi # container memory passed to docker run (default: 512Mi)
cpu: 1 # CPU shares passed to docker run (default: 1)
dockerfile: Dockerfile # Dockerfile path relative to repo root (default: Dockerfile)
buildContext: . # Docker build context (default: .)
env:
NODE_ENV: preview # inject env vars into the preview container
Step 5 — Verify
Comment /validate-previewops on any open PR. The bot calls the Hetzner API and confirms the token and SSH key name are valid.
Notes
- Each PR gets its own server (
prev-{owner}-{repo}-pr{number}), provisioned on-demand and destroyed on delete/close. - First deploy takes ~2–3 minutes (server boot + cloud-init Docker installation). Subsequent deploys to the same PR reuse the server and are faster (~30 s).
- The preview URL is
http://{server-ipv4}. The container is mapped to port 80 on the host — no port number needed in the URL. For HTTPS, place Caddy or an nginx reverse proxy in front. - Hetzner pricing is among the lowest of any provider (~€0.005/hr for a cx22).
Troubleshooting
| Error | Fix |
|---|---|
HETZNER_API_TOKEN env var is required |
Set the token in secrets |
HETZNER_SSH_KEY env var is required |
Set the private key in secrets |
hetzner providerConfig.sshKeyName is required |
Add sshKeyName to providerConfig |
SSH key not found from Hetzner API |
The name in providerConfig doesn't match the name registered in Hetzner |
| SSH connection refused | Server hasn't finished cloud-init yet — the provider retries automatically |