Skip to content

VPS Deployment

This guide walks you through deploying Pinchy on a fresh Linux VPS. It has been tested on Ubuntu 24.04, but any Linux distribution with Docker support will work.

If you’re coming from a provider-specific guide (Hetzner, DigitalOcean), you should already be connected to your server via SSH. All commands below run on the server, not on your local machine.

ResourceMinimumRecommended
RAM4 GB8 GB
CPU2 vCPUs4 vCPUs
Disk20 GB40 GB
OSUbuntu 22.04+Ubuntu 24.04

You also need:

  • A domain name pointing to your server’s IP address (for HTTPS)
  • An LLM provider API key (Anthropic, OpenAI, or Google)

Pinchy runs as three Docker containers:

ContainerRole
pinchyWeb UI, REST API, and WebSocket bridge. This is what users interact with.
openclawThe AI agent runtime — manages agents, sessions, plugins, and model calls. Only reachable internally.
dbPostgreSQL 17 database. Stores users, agents, settings, and audit logs. Only reachable internally.

All three start together via Docker Compose. Only the pinchy container publishes a port; the other two communicate over an internal Docker network.

  1. Install Docker

    If Docker is not already installed on your server:

    Terminal window
    apt-get update
    apt-get install -y docker.io docker-compose-v2

    Verify the installation:

    Terminal window
    docker --version # Should show: Docker version 24+
    docker compose version # Should show: Docker Compose version v2+

    For non-Ubuntu distributions, follow the official Docker installation guide.

    What do these commands do?- apt-get update — refreshes the list of available software packages - apt-get install -y — installs the listed packages (-y means “yes, don’t ask for confirmation”): - docker.io — Docker Engine, the tool that runs containers - docker-compose-v2 — Docker Compose, which starts multiple containers together

  2. Download the Docker Compose file

    Terminal window
    mkdir -p /opt/pinchy
    curl -fsSL https://raw.githubusercontent.com/heypinchy/pinchy/v0.5.4/docker-compose.yml -o /opt/pinchy/docker-compose.yml
  3. Pin your secrets before first start

    Pinchy uses three secrets:

    SecretPurpose
    DB_PASSWORDPostgreSQL database password
    BETTER_AUTH_SECRETSigns user sessions
    ENCRYPTION_KEYEncrypts LLM provider API keys at rest (AES-256-GCM)

    If you don’t set them, Pinchy auto-generates BETTER_AUTH_SECRET and ENCRYPTION_KEY and persists them as files in a Docker volume (pinchy-secrets). This works fine for normal operation — but if that volume is ever deleted (e.g. during a server migration or an accidental docker compose down -v), the generated secrets are lost and encrypted data becomes unreadable. DB_PASSWORD defaults to a weak development value.

    For production, pin all three explicitly:

    Terminal window
    nano /opt/pinchy/.env

    Add these lines (run openssl rand -hex 32 three times to generate each value):

    Terminal window
    DB_PASSWORD=<output of openssl rand -hex 32>
    BETTER_AUTH_SECRET=<output of openssl rand -hex 32>
    ENCRYPTION_KEY=<output of openssl rand -hex 32>

    Save and lock down the file:

    Terminal window
    chmod 600 /opt/pinchy/.env
  4. Pull and start Pinchy

    Terminal window
    cd /opt/pinchy
    docker compose pull
    docker compose up -d

    Pulling the pre-built images takes about 30 seconds.

    What do these commands do?- docker compose pull — downloads the pre-built Pinchy images - docker compose up -d — starts all services in the background

  5. Verify all services are running

    Terminal window
    docker compose ps

    You should see three services — pinchy, openclaw, and db — all with status Up or healthy.

    Check the Pinchy logs to make sure everything started correctly:

    Terminal window
    docker compose logs pinchy

    Look for: Pinchy ready on http://localhost:7777

  6. Open the setup wizard

    Pinchy binds to 127.0.0.1:7777 by default so it is only reachable from localhost. Open a temporary SSH tunnel from your local machine to reach it:

    Terminal window
    ssh -L 7777:localhost:7777 root@<your-server-ip>

    Then visit http://localhost:7777 in your browser while that SSH session is open.

    The setup wizard will guide you through creating your admin account and configuring your first LLM provider. You’ll need your API key (from Anthropic, OpenAI, or Google) ready.

That’s it — Pinchy is running! The rest of this guide covers production hardening, which you should do before using Pinchy with real data.

The steps above get Pinchy working, but for production use you should add HTTPS, a firewall, and a domain lock.

Use a reverse proxy that handles HTTPS for you. We recommend Caddy because it automatically gets and renews SSL certificates — zero configuration.

Pinchy already binds to 127.0.0.1:7777 by default — only reachable from localhost. This is exactly what you want when Caddy sits in front of it. No port configuration needed.

Install Caddy:

Terminal window
apt-get install -y caddy

Edit the Caddy configuration file:

Terminal window
nano /etc/caddy/Caddyfile

Replace the contents with (use your actual domain):

pinchy.example.com {
reverse_proxy localhost:7777
}

Save the file (Ctrl+O, Enter, Ctrl+X in nano) and restart Caddy:

Terminal window
systemctl restart caddy

Caddy automatically provisions Let’s Encrypt certificates for your domain. After a few seconds, visit https://pinchy.example.com — you should see Pinchy with a valid HTTPS certificate.

What’s a reverse proxy and why do I need one?

A reverse proxy sits between the internet and Pinchy. When someone visits your domain:

  1. Their browser connects to Caddy on port 443 (HTTPS)
  2. Caddy handles the SSL certificate and encryption
  3. Caddy forwards the request to Pinchy on port 7777 (internal only)
  4. Pinchy sends the response back through Caddy

This way, Pinchy never needs to deal with SSL certificates directly.

See the Hardening Guide for nginx examples and advanced configuration.

Once HTTPS is working, lock Pinchy to your domain so it only responds to requests on that exact hostname. This enables secure cookies, HSTS, and origin checks as a package. See the HTTPS & Domain Lock guide for the full walkthrough — in short: open Settings → Security and click Lock to this domain.

A firewall controls which network ports are accessible from the internet. Lock down your server to only the ports you need:

Terminal window
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

Type y when asked to confirm. This allows:

  • SSH (port 22) — so you can still log in to your server
  • HTTP (port 80) — needed for Caddy’s certificate renewal
  • HTTPS (port 443) — how users access Pinchy

Everything else is blocked — including port 7777, which is now only reachable by Caddy on the same machine.

Keep the host OS patched without manual intervention:

Terminal window
apt-get install -y unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades

Select Yes when prompted. Ubuntu will now install security updates automatically and reboot overnight when a kernel update requires it.

See the Hardening Guide for further production hardening — Docker security options, network isolation, disk encryption, monitoring, and backup automation.

Swap is disk space that your server can use as extra memory when RAM runs low. On servers with only 4 GB RAM, adding swap provides a safety net during heavy agent workloads:

Terminal window
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

The last line ensures swap is re-enabled after a server reboot.

Terminal window
cd /opt/pinchy
docker compose exec db pg_dump -U pinchy pinchy > backup-$(date +%Y%m%d).sql
curl -fsSL https://raw.githubusercontent.com/heypinchy/pinchy/v0.5.4/docker-compose.yml -o docker-compose.yml
docker compose pull
docker compose up -d
docker image prune -f

Pinchy runs database migrations automatically on startup — no manual steps needed. docker image prune -f after up -d removes the previous image layer left dangling by pull; without it, a long-running deployment slowly fills its root volume with <none>:<none> layers from prior upgrades. See the Upgrade Guide for version-specific notes, rollback instructions, when to escalate to docker image prune -a -f, and how to set up automated backups.

Docker Compose makes logs easy to access:

Terminal window
# All services
docker compose logs -f
# Single service (pinchy, openclaw, or db)
docker compose logs pinchy --tail 100
# Filter for errors
docker compose logs pinchy 2>&1 | grep -i error

Check the logs:

Terminal window
docker compose logs pinchy

Common causes:

  • Database not ready — wait a few seconds, the container will auto-restart
  • Port already in use — check with ss -tlnp | grep 7777

WebSocket connection errors in the browser

Section titled “WebSocket connection errors in the browser”

If you’re behind a reverse proxy, make sure it supports WebSocket upgrades. For nginx, add:

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

Caddy handles WebSocket upgrades automatically.

Check if the OpenClaw container is running:

Terminal window
docker compose ps openclaw
docker compose logs openclaw --tail 50

Common causes:

  • No LLM provider configured — open Settings and add an API key
  • API key invalid or out of credits — the chat shows a specific error card with details