Private by default. The web app has no built-in authentication layer. Keep it private via SSH
tunnel or Tailscale unless you add an external auth gate (Cloudflare Access, HTTP basic auth,
VPN). Only the gateway’s
/hooks/* endpoints need to be publicly reachable — and only if you use
Telegram, Discord, or WhatsApp integrations.VPS requirements
| Minimum | Recommended | |
|---|---|---|
| CPU | 1 vCPU | 1–2 vCPU |
| RAM | 512 MB | 1 GB+ |
| Disk | 1 GB | 5 GB+ |
| OS | Linux x86_64 or arm64 | Ubuntu 22.04 LTS / 24.04 LTS |
Architecture
127.0.0.1 by default. Only the reverse proxy is publicly reachable.
Quickstart
Initialize your vault
~/.grind/, generates an encryption key, and sets up your profile. Back up the key immediately — see Step 2 below.Step 1: Install Grind
See Installation for all options. On a Linux server:Step 2: Initialize
Step 3: Configure environment
Set production values in an env file. Grind readsprocess.env, so any mechanism works (systemd EnvironmentFile, Docker env_file, shell export).
/etc/grind.env (or ~/.grind/.env):
Step 4: Keep services running (systemd)
Enable user lingering
Grind’sgateway start installs a systemd user unit automatically. On a headless VPS, enable linger so user services survive logout and start on boot:
Web app unit
The web app has no built-in autostart. Create a user service:Alternative: system-level units
If you prefer system-level services (no linger required):If you use system-level units, run
grindxp gateway disable first to remove the user-level unit
that gateway start installed — otherwise both will compete to run.Alternative: PM2
Step 5: Reverse proxy + TLS
The reverse proxy terminates TLS and routes traffic to local services. Recommended routing:hooks.example.com/hooks/*→http://127.0.0.1:5174— webhook endpoints (public, signature-verified by each integration)app.example.com→http://127.0.0.1:3000— web app (add an auth gate before exposing publicly)
- Caddy
- Nginx
- Traefik
- SSH Tunnel (personal access)
Caddy handles TLS automatically via Let’s Encrypt — no For other distros see the official Caddy install docs.
certbot needed./etc/caddy/Caddyfile:Step 6: Firewall
Allow only SSH and the reverse proxy. Everything else stays closed.- UFW (Debian / Ubuntu)
- firewalld (Fedora / RHEL)
- nftables (Arch / generic)
- Provider firewall
:3000) and gateway (:5174) are not opened — only the reverse proxy on the same host can reach them via localhost.
Step 7: Verify
Webhooks
If you use Telegram, Discord, or WhatsApp integrations, the gateway needs a public HTTPS URL. The reverse proxy above handles TLS termination. Register these webhook URLs with each provider:| Integration | Webhook URL |
|---|---|
| Telegram | https://hooks.example.com/hooks/telegram |
| Discord | https://hooks.example.com/hooks/discord |
https://hooks.example.com/hooks/whatsapp | |
| Generic | https://hooks.example.com/hooks/inbound |
grindxp integrations to connect credentials. Set the corresponding verification secrets in your env file — requests without valid signatures are rejected:
Docker
Initialize Grind on your local machine first (grindxp init), then copy ~/.grind/ to the server before running containers.
docker-compose.yml:
.env:
Platform examples
Hetzner
Hetzner
The CX22 (2 vCPU, 4 GB RAM, ~€4/month) is the recommended starting point. CX11 (2 GB) works for personal use.
- Open the Hetzner Cloud Console and create a server. Choose any supported Linux distro (Ubuntu 24.04 recommended) and add your SSH key during provisioning.
- SSH in as root and create a non-root user:
- Follow the Quickstart then the full steps above.
- Point DNS A records at the VPS IP:
hooks.example.com → <vps-ip>(public, for webhooks)app.example.com → <vps-ip>(optional — keep private unless you add auth)
- Use the Caddy config for automatic TLS with zero extra configuration.
- In the Cloud Console, add a Cloud Firewall that allows TCP 22, 80, 443 inbound — all other ports blocked.
DigitalOcean
DigitalOcean
A Basic Droplet ($6/month, 1 vCPU, 1 GB RAM) covers personal Grind usage.
- Create a Droplet (Ubuntu 24.04 recommended; any supported Linux distro works). In Networking → Firewalls, allow TCP 22, 80, 443 inbound.
- SSH in and follow the Quickstart.
- Set DNS A records in Networking → Domains.
- Use Caddy or Nginx — both work well.
DigitalOcean’s managed firewall and a host-level firewall (UFW, firewalld) are independent
layers. Configure one or the other — not both — to avoid hard-to-debug conflicts. The managed
firewall is easier to audit from the cloud console.
Oracle Cloud (Always Free)
Oracle Cloud (Always Free)
The Oracle Cloud Free Tier includes Ampere A1 ARM compute (up to 4 OCPUs + 24 GB RAM total) and 200 GB block storage — permanently free, no expiry.
- Sign up and create a Compute instance. Choose Ubuntu 22.04 or later on Ampere A1 (ARM / aarch64). Grind and Bun both have full arm64 support.
- Open ports 80 and 443 in the OCI Security List (Networking → Virtual Cloud Networks → your VCN → Security Lists). The OS firewall alone is not sufficient on OCI — the Security List sits at the hypervisor level and must also allow the ports.
- Follow the Quickstart.
- Use Caddy — it’s the easiest path on ARM.
Fly.io
Fly.io
Fly.io’s Create a persistent volume for Fly handles TLS automatically. The web app is not deployed here by default — expose it separately or access via SSH tunnel with
shared-cpu-1x with 512 MB RAM works for Grind. See the fly.toml reference for all config options.fly.toml:~/.grind/:fly ssh console.Any VPS (generic)
Any VPS (generic)
The guide above works on any Linux VPS. Key points:
- OS: Any modern Linux distro with systemd (Ubuntu 20.04+, Debian 11+, Fedora 38+, RHEL 9+, Arch).
- Bun:
curl -fsSL https://bun.com/install | bash— see Bun install docs for other methods. - Grind:
bun install -g grindxp - Autostart:
loginctl enable-linger $USERthengrindxp gateway start, plus a user systemd unit for the web app. - Firewall: use your provider’s managed firewall panel if one exists — don’t run both it and a host-level firewall.
- TLS: Caddy is the simplest option on any provider — it auto-provisions Let’s Encrypt certificates.
Backup
Back up these two files regularly. Withoutconfig.json the vault is permanently unrecoverable:
| File | Contains |
|---|---|
~/.grind/config.json | Encryption key + all settings |
~/.grind/vault.db | Quest data, XP, streaks, conversations |
Troubleshooting
First 60 seconds if something is broken
systemctl --user fails: “Failed to connect to bus”
On a headless VPS without a login session, the D-Bus session bus is unavailable. Fix:
Gateway port already in use
Reverse proxy returns 502
The service is not running or is bound to the wrong address:127.0.0.1 (or 0.0.0.0) and running before the proxy can reach them.
Webhook 401 / signature errors
The verification secret doesn’t match what was registered with the provider. Re-rungrindxp integrations to update credentials, and ensure the matching env var is set:
Encryption key missing after reinstall
The key is in~/.grind/config.json. If you deleted it without backing it up, the vault is unrecoverable. This is why backing up config.json before anything else is critical.