---
title: Deploy Pinchy on Hetzner Cloud
description: Deploy a self-hosted AI agent platform on Hetzner Cloud. Step-by-step guide from server creation to running Pinchy with Docker.
---

import { Aside, Code, Steps } from "@astrojs/starlight/components";
import cloudInitScript from "../../../snippets/cloud-init.yml?raw";

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.

## Why Hetzner for self-hosted AI agents?

- **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.

## What you'll need

- A [Hetzner Cloud account](https://accounts.hetzner.com/) (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

## Create your server

<Steps>

1.  **Open the Hetzner Cloud Console**

    After signing up or logging in at [accounts.hetzner.com](https://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.

    <Aside type="tip">
      For teams with more than 5 users or heavy agent workloads, pick the next
      size up (8 GB RAM, 4 vCPUs) for smoother performance.
    </Aside>

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.

    <details>

    <summary>I don't have an SSH key yet</summary>

    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:**

    ```bash
    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:

    ```bash
    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:

    ```bash
    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.

    </details>

    <Aside type="caution">
      Skipping the SSH key leaves the server with no login path at all.
      Hetzner's web console shows a `login:` prompt, but there's no password —
      the only recovery is to use "Reset root password" in the Cloud Console
      actions menu. Don't skip this step.
    </Aside>

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:

    <Code code={cloudInitScript} lang="yaml" title="cloud-init.yml" />

    <details>

    <summary>What does this script do?</summary>

    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](/cloud-init.yml) separately.

    </details>

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.

</Steps>

## Production setup

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>`.

<details>
<summary>How do I set up SSH access?</summary>

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](https://aka.ms/terminal)
- **Linux** — Open your terminal app (usually Ctrl+Alt+T)

**2. Check if you already have an SSH key:**

```bash
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:**

```bash
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:

```bash
cat ~/.ssh/id_ed25519.pub
```

In the Hetzner Console, go to **Security** in the left sidebar → **SSH Keys** → **Add 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:

```bash
ssh root@<your-server-ip>
```

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

</details>

### Set up HTTPS with Caddy

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:

```bash
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:

```bash
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:

<Aside type="caution">
  This only works when your `docker-compose.yml` passes `BETTER_AUTH_URL`
  through to the `pinchy` service. If your server was installed with an older
  compose file that does not include it, fetch a newer compose file or add the
  environment variable via `docker-compose.override.yml` before restarting.
</Aside>

```bash
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.

<Aside type="tip">
  Pinchy already listens only on `127.0.0.1:7777` — Caddy is the single entry
  point from the internet. No `PINCHY_PORT` changes needed.
</Aside>

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

```bash
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.

### Lock your domain

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.

<Aside type="note">
  Secrets (database password, session secret, encryption key) were automatically
  generated and saved to `/opt/pinchy/.env` when your server was first created.
  They are already pinned — no manual step needed.
</Aside>

## 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):

| Type | Name                                   | Value                    |
| ---- | -------------------------------------- | ------------------------ |
| A    | `pinchy` (or your preferred subdomain) | Your server's IP address |

<details>
<summary>What's an A record?</summary>

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.

</details>

## Hetzner-specific tips

### Snapshots for backups

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**

### Cloud firewall

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.

### Volumes for persistent data

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](/guides/mount-data-directories/) for details.

### Scaling up

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.
