Skip to content

Deploy Pinchy on Hetzner Cloud

Hetzner Cloud is an excellent choice for running Pinchy. Their servers run in EU data centers (Germany and Finland), which keeps your data under EU jurisdiction — no overseas transfers, no third-party cloud AI providers touching your conversations. Combined with strong price-to-performance ratios, it’s the go-to option for European teams that take data sovereignty seriously.

  • EU data centers — Falkenstein, Nuremberg, Helsinki. Your AI conversations stay in the EU.
  • Price — A server with 4 GB RAM and 2 vCPUs costs under €4/month (including IPv4). Enough to run Pinchy with a small team.
  • Predictable pricing — No surprise bandwidth charges. 20 TB outbound traffic included.
  • ISO 27001 certified — Relevant if your compliance team asks where the servers are.
  • A Hetzner Cloud account (free to create, pay-as-you-go)
  • A domain name (optional for testing, required for production HTTPS)
  • An LLM provider API key (Anthropic, OpenAI, or Google) — you’ll enter this in Pinchy’s setup wizard
  1. Open the Hetzner Cloud Console

    After signing up or logging in at accounts.hetzner.com, click the grid icon (⊞) in the top-right corner and select Console. This is where you manage your servers.

  2. Create or select a project

    You’ll see your projects on the dashboard. Create a new one (e.g., “Pinchy”) or click an existing project to open it.

  3. Start creating a server

    Inside your project, click the red Create Resource button in the top-right corner and select Servers.

  4. Choose server type

    Under Shared Resources, select Cost-Optimized, then pick x86 (Intel/AMD) as the architecture.

    Scroll down to the server sizes and select the smallest option with at least 4 GB RAM and 2 vCPUs. This is the minimum for Pinchy.

  5. Choose a location

    Pick any location — all are fine for Pinchy:

    • Falkenstein or Nuremberg (Germany)
    • Helsinki (Finland)
    • Ashburn, VA or Hillsboro, OR (US)
    • Singapore

    For EU data residency, choose one of the European locations.

  6. Choose the image

    Under Image, make sure OS Images is selected and pick Ubuntu (the latest LTS version).

  7. Add your SSH key (required)

    Scroll to the SSH keys section and attach an SSH key. This is required — without it you can’t upgrade Pinchy later, hand out production secrets, or enable HTTPS. Hetzner never sets a root password on Ubuntu cloud images, so the SSH key is your only way in.

    I don’t have an SSH key yet

    An SSH key is a pair of files that proves you are you when connecting to a server. It replaces passwords and is far more secure. Creating one takes under a minute.

    On your computer, open a terminal:

    • Mac — open Terminal (⌘+Space, type “Terminal”)
    • Windows — open PowerShell (Start menu)
    • Linux — any terminal app

    Check if you already have a key:

    Terminal window
    cat ~/.ssh/id_ed25519.pub

    If you see a line starting with ssh-ed25519, you already have a key — copy the output and skip to the next step. If you get “No such file or directory”, create one:

    Terminal window
    ssh-keygen -t ed25519 -C "your-email@example.com"

    Press Enter three times to accept defaults (empty passphrase is fine for a throwaway demo; use one for production). Then display and copy the public key:

    Terminal window
    cat ~/.ssh/id_ed25519.pub

    Add it to Hetzner:

    Back in the Cloud Console, the SSH keys section has an Add SSH Key button. Paste the full public key (the ssh-ed25519 AAAA… line, including your email), give it a name, and save. It now appears in the list — tick its checkbox to attach it to this server.

  8. Paste the setup script

    Scroll down past Volumes, Firewalls, Backups, Placement groups, and Labels until you reach Cloud config — it’s near the very bottom. Paste the following script into the “Cloud-init configuration” text area:

    cloud-init.yml
    #cloud-config
    # Pinchy v0.5.4 — Automated VPS Setup
    # https://docs.heypinchy.com/guides/vps-deployment/
    #
    # This cloud-init script installs Caddy (reverse proxy), Docker, and Pinchy,
    # configures a firewall, and adds swap. Paste it into the "User Data" field
    # when creating your server.
    #
    # After boot, visit http://<your-server-ip>. Caddy shows a loading page while
    # images are pulling, then seamlessly hands off to Pinchy — no refresh needed.
    # To enable HTTPS later, replace `:80` in /etc/caddy/Caddyfile with your domain
    # and run `systemctl reload caddy` — Let's Encrypt is automatic.
    runcmd:
    # Add Caddy's official Cloudsmith apt repository (GPG-verified)
    - apt-get update -qq
    - apt-get install -y -qq debian-keyring debian-archive-keyring apt-transport-https curl gpg
    - curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
    - curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
    - apt-get update -qq
    # Stage loading page for Caddy's fallback upstream
    - mkdir -p /var/www/pinchy-loading
    - curl -fsSL https://github.com/heypinchy/pinchy/releases/download/v0.5.4/installing.html -o /var/www/pinchy-loading/index.html
    - sed -i "s/INSTALL_START_TIME/$(date +%s)000/" /var/www/pinchy-loading/index.html
    # Write Caddyfile BEFORE installing caddy, so Caddy's first start uses our config
    - mkdir -p /etc/caddy
    - |
    cat > /etc/caddy/Caddyfile <<'EOF'
    # Pinchy reverse proxy
    # Primary upstream: Pinchy on 127.0.0.1:7777
    # Fallback upstream: local loading page on 127.0.0.1:9999
    #
    # lb_policy first → prefer primary; fall back only when primary is unreachable.
    # lb_try_duration 1s → fail fast so users never wait for a dead backend.
    # fail_duration 5s → mark unreachable backend as dead for 5s before retry.
    #
    # To enable HTTPS: replace `:80` with your domain (e.g. pinchy.example.com)
    # and run `systemctl reload caddy`. Caddy provisions Let's Encrypt automatically.
    :80 {
    reverse_proxy 127.0.0.1:7777 127.0.0.1:9999 {
    lb_policy first
    lb_try_duration 1s
    fail_duration 5s
    }
    }
    :9999 {
    bind 127.0.0.1
    handle /api/* {
    respond 503
    }
    handle {
    root * /var/www/pinchy-loading
    try_files /index.html
    file_server
    }
    }
    EOF
    # Install Caddy, Docker, and UFW in one apt call (Caddy starts with our Caddyfile).
    # --force-confold keeps our pre-staged /etc/caddy/Caddyfile without dpkg's
    # interactive conffile prompt, which would otherwise fail cloud-init (no stdin)
    # and leave the caddy package in iU state (postinst skipped, `caddy` user never
    # created, systemd start fails with exit 217/USER).
    - DEBIAN_FRONTEND=noninteractive apt-get install -y -qq -o Dpkg::Options::=--force-confold caddy docker.io docker-compose-v2 ufw
    - systemctl enable --now caddy
    - systemctl reload caddy
    # Firewall
    - ufw allow OpenSSH
    - ufw allow 80/tcp
    - ufw allow 443/tcp
    - ufw --force enable
    # Docker
    - systemctl enable docker
    - systemctl start docker
    # Swap (recommended for 4 GB servers)
    - fallocate -l 2G /swapfile
    - chmod 600 /swapfile
    - mkswap /swapfile
    - swapon /swapfile
    - echo '/swapfile none swap sw 0 0' >> /etc/fstab
    # Generate pinned secrets — Pinchy keeps its secure 127.0.0.1:7777 default,
    # reachable only via Caddy on the same host.
    - mkdir -p /opt/pinchy
    - echo "PINCHY_VERSION=v0.5.4" >> /opt/pinchy/.env
    - echo "DB_PASSWORD=$(openssl rand -hex 32)" >> /opt/pinchy/.env
    - echo "BETTER_AUTH_SECRET=$(openssl rand -hex 32)" >> /opt/pinchy/.env
    - echo "ENCRYPTION_KEY=$(openssl rand -hex 32)" >> /opt/pinchy/.env
    - chmod 600 /opt/pinchy/.env
    # Download compose file and pull images (Caddy serves loading page throughout)
    - curl -fsSL https://raw.githubusercontent.com/heypinchy/pinchy/v0.5.4/docker-compose.yml -o /opt/pinchy/docker-compose.yml
    - cd /opt/pinchy && docker compose pull
    # Start Pinchy — Caddy automatically swaps to the primary upstream within 5s
    - cd /opt/pinchy && docker compose up -d
    # Wait for Pinchy to be healthy (polled directly, not through Caddy)
    - for i in $(seq 1 120); do curl -sf http://127.0.0.1:7777/api/health > /dev/null 2>&1 && break; sleep 2; done
    What does this script do?

    This is a cloud-init script — a standard way to configure servers on first boot. When your server starts, it automatically:

    1. Installs Caddy, Docker, and a firewall
    2. Opens only the ports needed (SSH, HTTP, HTTPS)
    3. Adds 2 GB of swap space (a safety net for heavy agent workloads)
    4. Configures Caddy as a reverse proxy with a built-in loading page — so visitors see a friendly page during install and through any future Pinchy restart
    5. Downloads and starts Pinchy from pre-built Docker images (bound to 127.0.0.1:7777, reachable only via Caddy)

    No manual setup required. You can also download the script separately.

  9. Give your server a name and create it

    Scroll down to Name, enter pinchy (or whatever you prefer), and click Create & Buy now. Your server will be ready in about 20 seconds, but the setup script needs another about a minute to pull the Pinchy images.

  10. Note the IP address

    After creation, you’ll see the server’s detail page. The IPv4 address is shown at the top (e.g., 116.202.x.x). You’ll need this to access Pinchy.

  11. Open Pinchy

    Visit http://<your-server-ip> in your browser (replace <your-server-ip> with the actual IP address). You’ll see a loading page while Pinchy finishes installing, then it automatically hands off to the setup wizard — no refresh needed.

    The setup wizard will guide you through creating your admin account and configuring your first LLM provider.

The steps above get Pinchy running over plain HTTP on port 80. Before using it with real data, add HTTPS and lock your domain.

All commands below run on your server. You have two options to connect:

  1. Web console — On the server’s detail page in Hetzner, click the >_ icon (top-right) to open a browser-based terminal. No SSH key needed.
  2. SSH — Connect from your local terminal with ssh root@<your-server-ip>.
How do I set up SSH access?

SSH lets you run commands on your server from your local terminal. It’s faster and more comfortable than the web console.

1. Open a terminal on your computer:

  • Mac — Open Terminal (search in Spotlight with ⌘+Space)
  • Windows — Open PowerShell (search in Start menu) or install Windows Terminal
  • Linux — Open your terminal app (usually Ctrl+Alt+T)

2. Check if you already have an SSH key:

Terminal window
cat ~/.ssh/id_ed25519.pub

If you see a line starting with ssh-ed25519, skip to step 4. If you get “No such file”, continue with step 3.

3. Create an SSH key:

Terminal window
ssh-keygen -t ed25519 -C "your-email@example.com"

Press Enter to accept the default file location, then choose a passphrase (or press Enter twice for no passphrase).

4. Add the key to Hetzner:

Display your public key and copy the output:

Terminal window
cat ~/.ssh/id_ed25519.pub

In the Hetzner Console, go to Security in the left sidebar → SSH KeysAdd SSH key. Paste your key and give it a name.

5. Rebuild your server with the SSH key (or create a new one with the key selected). Then connect:

Terminal window
ssh root@<your-server-ip>

While connected, every command you type runs on your server. Type exit to disconnect.

Caddy is already installed and serving Pinchy on port 80 — the cloud-init script set it up. Switching to HTTPS is a single edit.

Open the Caddy configuration:

Terminal window
nano /etc/caddy/Caddyfile

Replace the :80 { line at the top with your domain (keep the rest of the file as is):

pinchy.example.com {
reverse_proxy 127.0.0.1:7777 127.0.0.1:9999 {
lb_policy first
lb_try_duration 1s
fail_duration 5s
}
}

Save the file (Ctrl+O, Enter, Ctrl+X) and reload Caddy:

Terminal window
systemctl reload caddy

Tell Pinchy its public HTTPS URL so auth callbacks and redirects are generated with your real domain. Use the same domain you put in the Caddyfile, including https:// and no trailing slash:

Terminal window
cd /opt/pinchy
grep -q '^BETTER_AUTH_URL=' .env \
&& sed -i 's|^BETTER_AUTH_URL=.*|BETTER_AUTH_URL=https://pinchy.example.com|' .env \
|| echo 'BETTER_AUTH_URL=https://pinchy.example.com' >> .env
docker compose up -d pinchy

After a few seconds, visit https://pinchy.example.com — Caddy automatically provisions a Let’s Encrypt certificate. The loading-page fallback on :9999 stays unchanged so users still see a friendly page if Pinchy ever becomes unreachable during an upgrade or restart.

Set the public Better Auth callback origin to the same HTTPS domain:

Terminal window
cd /opt/pinchy
printf '\nBETTER_AUTH_URL=https://pinchy.example.com\n' >> .env
docker compose up -d

BETTER_AUTH_URL is separate from Domain Lock. It is used for absolute callback and redirect URLs such as password reset, OAuth, and Telegram pairing.

Once HTTPS is working, open Settings → Security in the Pinchy web UI and enter your domain (e.g. pinchy.example.com).

After saving, Pinchy will reject requests from any other origin with a 403 error — including access via the raw server IP address. The domain lock should match the hostname in BETTER_AUTH_URL; together they keep security checks and generated callback URLs on the same public origin.

Section titled “Point your domain (optional but recommended)”

To use HTTPS, you need a domain pointing to your server. Add a DNS record at your domain registrar (the service where you registered your domain):

TypeNameValue
Apinchy (or your preferred subdomain)Your server’s IP address
What’s an A record?

An A record is a DNS entry that maps a domain name (like pinchy.example.com) to an IP address (like 116.202.1.23). When someone visits your domain, their browser looks up the A record to find your server.

Look for “DNS records” or “DNS settings” in your domain registrar’s dashboard.

Hetzner offers server snapshots (€0.012/GB/month). Take a snapshot after your initial setup — it lets you restore the entire server in minutes if something goes wrong.

Servers → your server → Snapshots → Create Snapshot

Hetzner offers a Firewall feature under Security → Firewalls. For defense in depth, configure the cloud firewall to only allow ports 22, 80, and 443 — in addition to the host firewall (ufw) that the setup script already configured.

For larger Knowledge Base deployments, you can attach a Hetzner Volume (up to 10 TB) and mount it as a data directory. See Mount Data Directories for details.

If you outgrow your server, you can resize to a larger type directly in the Hetzner console. Pinchy’s data lives in Docker volumes, so it survives server resizes. Plan for a brief downtime window (a few minutes) during the resize.