Why self-host PocketBase on a VPS
PocketBase is an open source backend-as-a-service distributed as a single binary: no separate Postgres, no Redis, no complex stack to orchestrate. Everything runs on SQLite, making it perfectly suited to an entry-level VPS.
By self-hosting, you retain full control over your user data and files, without depending on a managed Firebase or Supabase with quotas and latency that can be problematic depending on your region. A VPS gives you a fixed IP, a custom domain and backups that you fully control.
Because PocketBase writes to a single pb_data folder, your backup strategy comes down to copying that directory, which simplifies operations enormously compared to a classic database cluster. You can migrate your backend by moving a single folder.
Concrete benefits of self-hosting PocketBase
- A single binary to deploy: starts in seconds, minimal memory footprint (often less than 100 MB of RAM).
- Embedded SQLite database: zero database service to manage, backup by simply copying the
pb_datafolder. - Full authentication (email/password, OAuth2, OTP, LDAP) and granular access rules per collection.
- Real-time API via Server-Sent Events to automatically sync your clients without additional WebSocket.
- Complete web admin interface to manage collections, users, permissions and files without writing SQL.
- Data sovereignty: your users and their files stay on your infrastructure, behind your domain.
- Predictable cost: the VPS price does not change with traffic, unlike per-request billing.
Precise requirements: VPS, system and software
PocketBase is extremely lightweight. Here is what you need based on your load:
Absolute minimum: 1 vCPU, 512 MB of RAM. Sufficient for a development project or a small application with fewer than 1,000 active users.
Recommended in production: 1 vCPU, 1 GB of RAM. PocketBase handles a small to medium-sized project easily with this configuration; the SQLite cache and real-time traffic spikes are absorbed comfortably.
For high volumes: 2 vCPU, 2 GB of RAM. The limit of PocketBase is usually concurrent writes to SQLite, not CPU.
For storage, plan for 20 to 40 GB SSD NVMe depending on the volume of files uploaded by your users. Since SQLite is a single file, an NVMe disk noticeably improves concurrent write performance — avoid network drives (NFS, CIFS) which can corrupt the database when locks are mishandled.
Required software: Docker 24+ and Docker Compose v2 (or the PocketBase binary alone with systemd), a domain name or subdomain pointing to the VPS IP via an A record, a reverse proxy (Caddy, Nginx or Traefik) to handle TLS.
Deploy PocketBase on VPS step by step
Prepare the VPS and DNS
Connect via SSH, update the system with
apt update && apt upgrade -y, then install Docker with the official script:curl -fsSL https://get.docker.com | sh. Add your user to the Docker group withusermod -aG docker $USERto avoidsudoon every command.Then create an A DNS record pointing
app.your-domain.comto the public IP of your VPS. Propagation takes from a few minutes to 24 hours — verify withdig app.your-domain.com.Create the file structure
Create a dedicated directory and the compose file:
mkdir -p /opt/pocketbase && cd /opt/pocketbaseCreate the
docker-compose.ymlfile with the following content:services: pocketbase: image: ghcr.io/muchobien/pocketbase:latest restart: unless-stopped volumes: - ./pb_data:/pb_data expose: - "8090"Do not publish port
8090directly to the Internet (noports:) — let the reverse proxy handle it.Configure HTTPS with Caddy (recommended reverse proxy)
Caddy automatically obtains and renews Let's Encrypt certificates. Create
/opt/pocketbase/Caddyfile:app.your-domain.com { reverse_proxy pocketbase:8090 }Add the Caddy service in
docker-compose.yml:caddy: image: caddy:2-alpine restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - caddy_data:/data depends_on: - pocketbase volumes: caddy_data:Caddy enables WebSockets (used by PocketBase real-time subscriptions) by default. If you prefer Nginx, add the
UpgradeandConnectionheaders in yourlocation /so SSE connections pass through correctly.Start and verify
Start the stack with
docker compose up -dthen follow the logs:docker compose logs -f pocketbaseYou should see a line indicating PocketBase is listening on
0.0.0.0:8090and the URL/_/for the admin interface. Verify that HTTPS works:curl -I https://app.your-domain.com/_/should returnHTTP/2 200.Create the super-admin account
Open
https://app.your-domain.com/_/in your browser. On first access, PocketBase prompts you to create a super-admin account (email + password). Do this immediately after startup — before making the URL public — because anyone reaching/_/can create this first account on a fresh instance.Once logged in, explore the interface: Collections, Users, Logs and Settings.
Configure OAuth authentication (Google, GitHub, GitLab)
PocketBase natively supports OAuth2 with several providers: Google, GitHub, GitLab, Discord, Twitter/X, Microsoft, Apple and others. Configuration is done entirely from the admin interface, without touching code.
For Google OAuth:
1. Open the Google Cloud Console, create a project and enable the "OAuth consent screen" API.
2. Under "Credentials", create an "OAuth 2.0 Client ID" of type "Web application".
3. Add https://app.your-domain.com/api/oauth2-redirect as an authorized redirect URI.
4. Copy the Client ID and Client secret.
5. In PocketBase /_/, go to Settings → Auth providers → Google, enable it and paste your credentials.
For GitHub OAuth:
1. On GitHub, go to Settings → Developer settings → OAuth Apps → New OAuth App.
2. Set https://app.your-domain.com as the Homepage URL and https://app.your-domain.com/api/oauth2-redirect as the Authorization callback URL.
3. Copy the Client ID and generate a Client Secret.
4. Enable GitHub under Settings → Auth providers in PocketBase.
Each active provider will automatically appear on the login page generated by PocketBase. Your frontend only needs to call pb.collection('users').authWithOAuth2({ provider: 'google' }) via the JavaScript SDK.
Automatic backups: SQLite and files
PocketBase stores everything in the pb_data folder: the SQLite database (pb_data/data.db), uploaded files (pb_data/storage/) and logs. A complete backup is simply archiving that folder.
Method 1 — simple cron: add this line to your crontab (crontab -e) for a daily archive at 3am:
0 3 * * * tar czf /var/backups/pocketbase-$(date +\%F).tar.gz /opt/pocketbase/pb_data && find /var/backups -name 'pocketbase-*.tar.gz' -mtime +7 -deleteThis command creates a dated archive and deletes backups older than 7 days.
Method 2 — built-in PocketBase backups: from the /_/ interface, go to Settings → Backups. PocketBase can create pb_data archives directly from the admin or via the API (POST /api/backups). You can download these archives or send them to an S3-compatible storage.
Method 3 — rclone to S3: for automatic off-site copying, use rclone:
0 4 * * * rclone sync /opt/pocketbase/pb_data your-remote:pocketbase-backup/your-remote is an rclone profile configured to Backblaze B2, AWS S3 or any compatible object storage. Always keep a copy off the server — if the disk fails, a local-only backup is useless.
SQLite WAL mode and performance
PocketBase enables SQLite WAL (Write-Ahead Logging) mode by default in recent versions. This mode allows concurrent reads during writes, preventing an insert from blocking all reads. If you experience timeouts under heavy load, check with PRAGMA journal_mode; in PocketBase's SQL admin — the response should be wal, not delete.
For high traffic, place pb_data on an NVMe volume and avoid any network file system. SQLite on NFS can corrupt the database when locks are mishandled.
Troubleshooting: common errors
Port already in use: if docker compose up fails with bind: address already in use on port 80 or 443, check with ss -tlnp | grep ':80' which service is using the port. An nginx or Apache installed on the host conflicts with Caddy. Stop it (systemctl stop nginx) or change its ports.
Permissions on pb_data: PocketBase runs as user nobody (UID 65534) in the official container. If you mount a folder created by root, the process cannot write. Fix with chown -R 65534:65534 /opt/pocketbase/pb_data.
/_/ inaccessible after startup: verify that the reverse proxy points to the Docker service name (pocketbase:8090) and not localhost:8090. In a Docker Compose network, services communicate by their name, not via localhost.
Updating PocketBase: change the image tag in docker-compose.yml (e.g. ghcr.io/muchobien/pocketbase:0.23) then docker compose pull && docker compose up -d. PocketBase performs schema migrations automatically on startup. Back up pb_data before any major update.
Let's Encrypt certificate not obtained: Caddy needs ports 80 and 443 accessible from the Internet for the HTTP-01 challenge. Check your firewall (ufw status) and your hosting provider's security group rules.
Going further: admin CLI, collections and hooks
Admin CLI: PocketBase exposes a CLI for common operations without going through the web interface. From the container: docker exec -it pocketbase_pocketbase_1 /pb/pocketbase --help. You can create a super-admin from the command line, run migrations or check the database health.
Custom collections: in /_/, create your collections (equivalent to tables) by defining typed fields (text, number, bool, date, file, relation, select, JSON). API rules are expressed in PocketBase syntax (@request.auth.id != "" to restrict to logged-in users, @request.auth.id = id to limit to one's own record).
Hooks (server-side JavaScript): since PocketBase v0.17, you can extend the backend with JavaScript scripts running server-side (pb_hooks/*.pb.js). Example uses: send an email when a user is created, validate complex data before insertion, trigger a webhook to Slack. These hooks are files mounted in the container:
volumes:
- ./pb_data:/pb_data
- ./pb_hooks:/pb_hooksJavaScript SDK: the official SDK (npm install pocketbase) simplifies calls from your React, Vue or mobile frontend. It handles authentication, token refresh and real-time subscriptions (pb.collection('tasks').subscribe('*', callback)).
PocketBase vs self-hosted alternatives
Scroll the table
| Criterion | PocketBase | Supabase self-hosted | Appwrite |
|---|---|---|---|
| Installation complexity | Very low (1 binary or 1 container) | High (docker-compose of 10+ services) | Medium (docker-compose of 6 services) |
| Minimum RAM | 512 MB | 4 GB+ | 2 GB+ |
| Database | Embedded SQLite | PostgreSQL | MariaDB |
| OAuth authentication | Yes (native) | Yes (GoTrue) | Yes (native) |
| Real-time | Server-Sent Events | WebSocket (Realtime server) | WebSocket |
| File storage | Yes (local + S3) | Yes (S3 compatible) | Yes (local + S3) |
| Horizontal scaling | No (single-file SQLite) | Yes (PostgreSQL) | Limited |
Official documentation
For advanced configuration and tool-specific options, refer to the official PocketBase documentation. This guide covers deployment on VPS; the editor documentation remains the reference for fine-tuning, major updates and specific use cases.
If you want to test PocketBase without Docker, download the Linux binary from github.com/pocketbase/pocketbase/releases, run ./pocketbase serve --http=0.0.0.0:8090 and create a systemd service so it restarts automatically. This approach is even lighter than Docker on small VPS.