Skip to content

Self-Hosting

Self-hosting gives you full control over your data, no rate limits, and the ability to customize the server. The default Docker stack includes Redis (event buffer), a UI sidecar (GHCR UI assets), and the PizzaPi server (relay API + web UI).


The fastest way to self-host is with a single command. No cloning, no config files — just Docker.

Terminal window
pizzapi web

This automatically:

  1. Clones the PizzaPi repo (if not already present) to ~/.pizzapi/web/repo
  2. Generates required secrets (including BETTER_AUTH_SECRET and VAPID keys) and a Docker Compose file in ~/.pizzapi/web/
  3. Uses the GHCR UI image by default — or the local dev stack (bun run dev / dev:ui) when you run --dev-ui from a local checkout (UI on port 5173)
  4. Builds and starts Redis + the relay server
  5. Serves the web UI at http://localhost:7492

On subsequent runs, it pulls the latest repo changes and refreshes the configured UI image tag before startup.

Terminal window
# Custom port (persisted for future runs)
pizzapi web --port 8080
# Set extra CORS origins (persisted)
pizzapi web --origins "https://example.com"
# Pull a specific GHCR UI tag
pizzapi web --tag main
# Build the UI locally (fallback mode)
pizzapi web --build
# Run the local dev UI stack (repo checkout only)
pizzapi web --dev-ui
# Run in the foreground
pizzapi web --foreground
# View / change config without starting
pizzapi web config
pizzapi web config set port 9000
pizzapi web config set extraOrigins "https://example.com"
# Management
pizzapi web logs # Tail container logs
pizzapi web status # Show running containers
pizzapi web stop # Stop the hub

All settings are persisted in ~/.pizzapi/web/config.json and applied on every start. CLI flags like --port and --origins update the config automatically — you only need to pass them once.

Persistent data (SQLite database) is stored in ~/.pizzapi/web/data/.


  1. Clone the repository

    Terminal window
    git clone https://github.com/Pizzaface/PizzaPi.git
    cd PizzaPi
  2. Start the stack

    Terminal window
    docker compose -f docker/compose.yml up -d

    This starts:

    ServicePortDescription
    redis6379Session event buffer (internal)
    uiSidecar that syncs the GHCR UI assets into a shared volume
    server7492Relay API + Web UI
  3. Open the web UI

    Navigate to http://localhost:7492 — you’ll see the PizzaPi interface.

  4. Create your account

    The first time you visit, register an account. Then run pizzapi setup on your dev machine pointing to http://localhost:7492.


Configure the server by setting environment variables in Docker Compose or your shell:

VariableDefaultDescription
PORT7492HTTP/WebSocket listen port
PIZZAPI_REDIS_URLRedis connection URL, e.g. redis://localhost:6379
PIZZAPI_BASE_URLhttp://localhost:5173Public URL of the UI, added to CORS trusted origins
PIZZAPI_EXTRA_ORIGINSComma-separated extra trusted origins (e.g. a Tailscale URL). Added to CORS and WebSocket auth without hardcoding hostnames. Example: https://myhost.ts.net,http://myhost.ts.net:5173

See the Environment Variables reference for the full list.

# docker/compose.override.yml (not committed — create locally)
services:
server:
environment:
PORT: "8080"
PIZZAPI_REDIS_URL: redis://redis:6379
ports:
- "8080:8080"

Hot-reloading dev server for contributors:

Terminal window
# Start both the API server and Vite UI dev server
docker compose -f docker/compose.yml --profile dev up
ServicePortDescription
redis6379Redis
server (dev)7492Bun dev server (hot-reload)
ui (dev)5173Vite UI dev server

Or run without Docker:

Terminal window
# Prerequisites: Bun, Redis running locally
bun install
bun run dev
# API → http://localhost:7492
# UI → http://localhost:5173

If you prefer not to use Docker:

  1. Install Bun

    Terminal window
    curl -fsSL https://bun.sh/install | bash
  2. Start Redis

    Terminal window
    # macOS via Homebrew
    brew install redis && brew services start redis
    # Ubuntu/Debian
    sudo apt install redis-server && sudo systemctl start redis
  3. Install dependencies and build

    Terminal window
    bun install
    bun run build
  4. Run the server

    Terminal window
    cd packages/server
    PIZZAPI_REDIS_URL=redis://localhost:6379 bun run start

The server uses SQLite (via Kysely) for user accounts and session metadata. The database file lives at packages/server/auth.db.

After upgrading PizzaPi, always run migrations before starting the server:

Terminal window
bun run migrate
Terminal window
# Simple backup — safe to copy while the server is stopped
cp packages/server/auth.db packages/server/auth.db.backup

When PizzaPi runs behind a reverse proxy (like Caddy, nginx, or Traefik in Docker), the server needs to trust proxy headers to correctly identify the real client IP for rate limiting and other security features.

If your reverse proxy is on the same host and connects to PizzaPi via loopback (127.0.0.1) — for example, a system-installed nginx or Caddy forwarding to localhost:7492 — no configuration is needed. The server auto-detects this and safely trusts X-Forwarded-For headers.

If your reverse proxy reaches PizzaPi via a non-loopback address — including any Docker Compose setup, a Docker overlay network, or a cloud load balancer — set PIZZAPI_TRUST_PROXY=true.

The simplest approach is to pass the variable inline — it will be forwarded to the Docker container:

Terminal window
PIZZAPI_TRUST_PROXY=true pizzapi web

Alternatively, add it to docker/compose.override.yml and pass both compose files explicitly (Docker only auto-merges compose.override.yml when you omit -f):

docker/compose.override.yml
services:
server:
environment:
PIZZAPI_TRUST_PROXY: "true"
Terminal window
docker compose -f docker/compose.yml -f docker/compose.override.yml up -d

By default, PizzaPi uses the rightmost X-Forwarded-For entry — the IP appended by the directly-connected proxy. This is safe against client spoofing for single-proxy setups.

If you have multiple trusted proxy hops (e.g. CDN → nginx → PizzaPi), set PIZZAPI_PROXY_DEPTH to the number of intermediate hops between the outermost trusted proxy and the real client:

Terminal window
# CDN + local reverse proxy = 1 intermediate hop
PIZZAPI_TRUST_PROXY=true PIZZAPI_PROXY_DEPTH=1 pizzapi web

Or with both compose files explicitly (required when using -f):

# docker/compose.override.yml — CDN + local proxy = 1 intermediate hop
services:
server:
environment:
PIZZAPI_TRUST_PROXY: "true"
PIZZAPI_PROXY_DEPTH: "1"
Terminal window
docker compose -f docker/compose.yml -f docker/compose.override.yml up -d
DepthTopologyXFF entry used
0 (default)Single proxy (Caddy, nginx, Traefik)Rightmost
1CDN + local proxy (2 total hops)Second from right
NN+1 total trusted hopsN positions from the right

Caddy automatically provisions TLS certificates via Let’s Encrypt:

/etc/caddy/Caddyfile
relay.example.com {
reverse_proxy localhost:7492
}
Terminal window
sudo systemctl reload caddy

Then update your CLI config to use the secure URL:

Terminal window
pizzapi setup
# Relay server URL: https://relay.example.com

Terminal window
cd PizzaPi
git pull
docker compose -f docker/compose.yml build
docker compose -f docker/compose.yml up -d

Always run bun run migrate if you’re running without Docker.


PizzaPi includes several built-in security measures for production deployments.

All server responses include security headers automatically:

  • X-Content-Type-Options: nosniff — prevents MIME-type sniffing
  • X-Frame-Options: DENY — prevents clickjacking (tunnel responses use SAMEORIGIN to allow same-origin iframing)
  • X-XSS-Protection: 0 — disables the legacy browser XSS filter (the recommended modern practice)
  • Referrer-Policy: strict-origin-when-cross-origin — limits referrer leakage
  • Permissions-Policy — restricts browser feature access

No configuration needed — these are applied to every response.

The server enforces body size limits to prevent denial-of-service:

  • API routes: 1 MB maximum
  • Attachment uploads: 50 MB maximum (configurable via PIZZAPI_ATTACHMENT_MAX_FILE_SIZE_BYTES)

Requests exceeding these limits receive a 413 Payload Too Large response.

The /health endpoint reports Redis and Socket.IO connectivity status. When Redis becomes unavailable, the web UI shows a dismissable degraded-mode banner with automatic retry. Sessions continue operating with reduced functionality (no cross-node relay, no event persistence) until Redis recovers.


The server supports browser push notifications for session events. Push is configured automatically — no additional setup required. Users can opt in from the web UI’s notification settings.