Self-Hosting

Pad is local-first. By default it runs on your laptop and binds to 127.0.0.1:7777 — that’s the recommended setup for solo use.

If you want to reach your own Pad from multiple devices you control — laptop + phone + work machine on the same LAN, Tailscale, or home VPN — running it on a small server or NAS works well. This page covers that single-user, multi-device setup.

Setting up Pad for a team? Pad Cloud is the supported path for multi-user setups — managed hosting, OAuth, team billing, automatic backups, no operator burden. The recipes below assume one user across multiple devices.

Running as a Service

systemd (Linux)

Create a service file at /etc/systemd/system/pad.service:

[Unit]
Description=Pad
After=network.target

[Service]
Type=simple
User=pad
Group=pad
ExecStart=/usr/local/bin/pad server start --host 0.0.0.0 --port 7777
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl enable pad
sudo systemctl start pad

The Pad data directory defaults to $HOME/.pad for the user running the service — with User=pad, that’s /home/pad/.pad/pad.db. Override with PAD_DATA_DIR=/srv/pad (or similar) in the service Environment= if you want a different location.

Docker

docker run -p 7777:7777 -v pad-data:/data ghcr.io/xarmian/pad

Or build the image yourself:

git clone https://github.com/xarmian/pad
cd pad
docker build -t pad .
docker run -p 7777:7777 -v pad-data:/data pad

Inside the container, Pad writes to /data — back up that volume. The default port publishing above (-p 7777:7777) exposes Pad on every host interface; for a single-user host bind to a specific interface (e.g. your Tailscale IP) or stick with -p 127.0.0.1:7777:7777 and reach Pad through a reverse proxy.

Reverse Proxy

A reverse proxy in front gives you HTTPS — required if you want session cookies to be flagged Secure (set PAD_SECURE_COOKIES=true).

Caddy

pad.example.com {
    reverse_proxy localhost:7777
}

Caddy automatically handles TLS via Let’s Encrypt. Simplest option.

Nginx

server {
    listen 443 ssl;
    server_name pad.example.com;

    ssl_certificate /etc/ssl/certs/pad.pem;
    ssl_certificate_key /etc/ssl/private/pad.key;

    location / {
        proxy_pass http://127.0.0.1:7777;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_buffering off;  # Required for SSE
    }
}

If you put any reverse proxy in front, also set PAD_TRUSTED_PROXIES to the proxy’s IP/CIDR so Pad will honor the forwarded headers (rate limits, audit logs, the bootstrap loopback check).

Data & Backups

Pad stores everything in a single SQLite database file. The path depends on how you’re running it:

SetupDatabase path
Local install (the default)~/.pad/pad.db
systemd (User=pad)/home/pad/.pad/pad.db
Docker/data/pad.db (inside the container; persisted by the named volume)

Override the location with PAD_DATA_DIR=/some/path — the database lives at $PAD_DATA_DIR/pad.db.

Backing up

# Local install — stop Pad first OR use SQLite's online backup
sqlite3 ~/.pad/pad.db ".backup '~/.pad/pad.db.backup'"

# systemd
sudo systemctl stop pad
sudo cp /home/pad/.pad/pad.db /home/pad/.pad/pad.db.backup
sudo systemctl start pad

# Docker — copy the volume contents
docker run --rm -v pad-data:/data -v "$PWD":/backup alpine 
    cp /data/pad.db /backup/pad.db.backup

The database file is the single source of truth. There are no external dependencies to back up.

Authentication

Pad uses user accounts with email + password. On a fresh install, there are no users — the server runs unauthenticated until the first admin is bootstrapped, then every API request and the web UI require auth.

First-time setup

# On the server host (loopback-only — see note below):
pad auth setup

pad auth setup is gated on the loopback check: it runs only when the request comes from the server itself. That’s deliberate — it creates the unauthenticated window during which the first admin is minted, and forces you to be physically on the server (or docker exec-ing into the container) to do it.

Subsequent logins

pad auth login    # email + password prompt
pad auth whoami   # show current user
pad auth logout

Credentials are stored at ~/.pad/credentials.json (mode 0600). The CLI auto-attaches the auth token to all API requests.

User accounts and roles

Once the first admin exists, additional users can be created two ways:

# Admin creates an account directly
pad auth register   # admin-only after setup

# Or invite a user to a workspace (preferred for collaboration)
pad workspace invite [email protected] --role editor
pad workspace join <code>   # accepter runs this

Roles:

RolePermissions
ownerFull workspace access including settings, member management
editorCreate / edit / delete items
viewerRead-only

API tokens

For CI, agents, or scripts:

# Create a token, optionally scoped
pad auth tokens create "ci-bot" --scopes '["read"]'

Scopes: ["*"] for full access (use sparingly), ["read"] for read-only. Unrecognized scopes are denied.

Security Considerations

  • Run pad auth setup immediately after first start. Until you do, the server accepts unauthenticated requests from loopback.
  • For anything reachable beyond your laptop, terminate TLS in front (reverse proxy) and set PAD_SECURE_COOKIES=true so session cookies are flagged Secure.
  • Set PAD_CORS_ORIGINS to the comma-separated origins of your web UI hosts. Without it, cookie-authenticated browser fetches from other origins are rejected.
  • The SQLite database is unencrypted on disk. Protect it with filesystem permissions (the default 0600 is correct).
  • The default bind is 127.0.0.1 — Pad won’t accept remote connections unless you opt in with --host 0.0.0.0 or PAD_HOST=0.0.0.0.