Skip to main content

Self-Hosting PocketBase: Minimal Backend 2026

·OSSAlt Team
pocketbasebackendself-hostingguide
Share:

PocketBase is the simplest backend you can deploy — a single Go binary with a built-in database (SQLite), admin UI, auth, file storage, and real-time subscriptions. No Docker needed. Download, run, done.

Why Self-Host PocketBase

Firebase's pricing model punishes success. The free Spark plan is generous for prototypes — 1 GB Firestore storage, 50K reads/day, 20K writes/day. But the moment you exceed those limits and move to the Blaze pay-as-you-go plan, costs can spike unpredictably. Firestore charges $0.06 per 100K reads and $0.18 per 100K writes; a modestly popular app hitting 10 million reads/month pays $6 in reads alone plus bandwidth charges. Supabase Cloud's free tier pauses inactive projects, forcing you to the $25/month Pro plan for any production app.

Self-hosting PocketBase costs the price of a VPS. A Hetzner CX11 at €3.29/month (or DigitalOcean's $6/month droplet) gives you a complete backend with zero per-operation pricing. Annual cost: under $80 versus Firebase's unpredictable scaling costs. For a side project hitting 5 million Firestore reads/month, that's $3/month in Firebase charges versus $3.29/month for a PocketBase server that handles the same load with far more predictable costs.

Operational simplicity is PocketBase's headline advantage. There is literally one file to manage. No Docker, no Kubernetes, no Redis, no separate database server. A single Go binary runs PocketBase — install it, run it as a systemd service, and you have a complete backend platform. Upgrades are as simple as replacing the binary and restarting the service.

SQLite performance surprises people. The common assumption is that SQLite can't handle real production load, but PocketBase's architecture (connection pooling, WAL mode enabled by default) handles thousands of concurrent reads comfortably. For most indie projects and small SaaS applications, SQLite's performance is not the bottleneck — network latency is. PocketBase's creator documented handling 10,000 concurrent connections on a $6/month VPS.

JavaScript-extensible. PocketBase supports custom hooks and routes written in JavaScript (via a built-in JS runtime), so you can add custom business logic, trigger webhooks on database events, or extend the API without rewriting the Go binary.

When NOT to self-host PocketBase: PocketBase uses SQLite, which has fundamental limitations for write-heavy workloads at scale. If your app has high-frequency concurrent writes (real-time collaborative editing, financial transactions at high volume, event streaming), PostgreSQL-backed alternatives like Supabase handle concurrent writes more gracefully. PocketBase also lacks built-in database clustering — it's a single-node solution.

Prerequisites

PocketBase has the lowest barrier to entry of any tool in this guide. The prerequisites are minimal, but understanding them properly prevents the most common gotchas. Before committing to a VPS, review your VPS options for self-hosting — PocketBase's single-binary design means even the cheapest servers work well.

Server specs: PocketBase's resource requirements are remarkably low. For 100-1,000 users, 512 MB RAM is genuinely sufficient — the Go runtime is memory-efficient and SQLite's WAL mode handles reads without loading entire tables into memory. Even the $3.29/month Hetzner CX11 (2 vCPU, 2 GB RAM) is overkill for most PocketBase deployments. CPU utilization stays low unless you're running complex JavaScript hooks.

No Docker required. This is intentional — PocketBase ships as a statically compiled binary with no external dependencies. Download the appropriate binary for your architecture (linux_amd64 for standard VPS servers), make it executable, and run it. The entire database, file storage, and configuration lives in a pb_data directory next to the binary.

Operating system: Any Linux distribution works. Ubuntu 22.04 LTS is the most documented choice. The systemd service setup (Step 7) uses standard Linux conventions compatible with any systemd-based distro.

Caddy for HTTPS: PocketBase runs on HTTP internally; Caddy provides SSL termination and proxying to HTTPS. Install Caddy with sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https && curl -1sLf 'https://dl.cloudflare.com/caddy/stable/debian.deb.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg && echo "deb [signed-by=/usr/share/keyrings/caddy-stable-archive-keyring.gpg] https://dl.cloudflare.com/caddy/stable/debian any-main" | sudo tee /etc/apt/sources.list.d/caddy-stable.list && sudo apt update && sudo apt install caddy.

Backup strategy: SQLite's entire state is in a single file — pb_data/data.db. This simplicity is a double-edged sword: one file to back up is convenient, but one corrupted file means total data loss. Set up automated backups before going to production.

Skills required: Basic Linux command line. PocketBase doesn't require Docker, Compose files, or container knowledge. If you can SSH into a server and run commands, you can deploy PocketBase.

Requirements

  • VPS with 512 MB RAM minimum
  • Domain name (e.g., api.yourdomain.com)
  • 5+ GB disk

Step 1: Download and Run

# Download latest release
wget https://github.com/pocketbase/pocketbase/releases/latest/download/pocketbase_0.25.0_linux_amd64.zip
unzip pocketbase_0.25.0_linux_amd64.zip

# Run
./pocketbase serve --http=0.0.0.0:8090

That's it. Admin UI at http://your-server:8090/_/, API at http://your-server:8090/api/.

Step 2: Reverse Proxy (Caddy)

# /etc/caddy/Caddyfile
api.yourdomain.com {
    reverse_proxy localhost:8090
}
sudo systemctl restart caddy

Step 3: Create Admin Account

  1. Open https://api.yourdomain.com/_/
  2. Create your admin account on first visit

Step 4: Create Collections (Tables)

In the admin UI:

  1. Click New collection
  2. Name it (e.g., posts)
  3. Add fields:
FieldTypeOptions
titleTextRequired, max 200
contentEditorRich text
publishedBoolDefault false
authorRelation→ users collection
imageFileMax 5MB, jpg/png/webp
tagsJSONArray of strings
  1. Configure API Rules (who can read/write):
    • List/Search: @request.auth.id != ""
    • View: "" (public)
    • Create: @request.auth.id != ""
    • Update: @request.auth.id = author.id
    • Delete: @request.auth.id = author.id

Step 5: Authentication

PocketBase includes built-in auth:

Email/password (default):

import PocketBase from 'pocketbase'

const pb = new PocketBase('https://api.yourdomain.com')

// Sign up
const user = await pb.collection('users').create({
  email: 'user@example.com',
  password: 'password123',
  passwordConfirm: 'password123',
  name: 'John Doe',
})

// Login
const auth = await pb.collection('users').authWithPassword(
  'user@example.com',
  'password123'
)
console.log(auth.token)

OAuth2 providers: Configure in Admin UI → Settings → Auth providers:

  • Google, GitHub, Microsoft, Apple, Discord, GitLab, Facebook, Twitter, Spotify
// OAuth2 login
const auth = await pb.collection('users').authWithOAuth2({ provider: 'google' })

Step 6: Use the API

import PocketBase from 'pocketbase'

const pb = new PocketBase('https://api.yourdomain.com')

// Create
const post = await pb.collection('posts').create({
  title: 'My First Post',
  content: '<p>Hello world</p>',
  published: true,
})

// List with filters
const posts = await pb.collection('posts').getList(1, 20, {
  filter: 'published = true',
  sort: '-created',
  expand: 'author',
})

// Real-time subscriptions
pb.collection('posts').subscribe('*', (e) => {
  console.log(e.action) // 'create', 'update', 'delete'
  console.log(e.record)
})

// File upload
const formData = new FormData()
formData.append('title', 'Post with image')
formData.append('image', fileInput.files[0])
const post = await pb.collection('posts').create(formData)

// Get file URL
const imageUrl = pb.files.getURL(post, post.image)

Step 7: Set Up as a Systemd Service

sudo nano /etc/systemd/system/pocketbase.service
[Unit]
Description=PocketBase
After=network.target

[Service]
Type=simple
User=pocketbase
Group=pocketbase
WorkingDirectory=/opt/pocketbase
ExecStart=/opt/pocketbase/pocketbase serve --http=0.0.0.0:8090
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl enable pocketbase
sudo systemctl start pocketbase

Step 8: Configure SMTP

In Admin UI → SettingsMail settings:

SettingValue
Sender addressnoreply@yourdomain.com
Sender nameMy App
SMTP hostsmtp.resend.com
SMTP port587
Usernameresend
Passwordre_your_api_key

Required for email verification, password reset, and notifications.

Production Hardening

Backups:

# Copy the SQLite database (daily cron)
# PocketBase stores everything in pb_data/
cp /opt/pocketbase/pb_data/data.db /backups/pocketbase-$(date +%Y%m%d).db

# Or use PocketBase's backup API
curl -X POST 'https://api.yourdomain.com/api/backups' \
  -H 'Authorization: admin-token'

PocketBase's single-file SQLite database is easy to back up but critical to protect. Use automated server backups with restic to push encrypted, deduplicated copies of pb_data/ to offsite storage daily. SQLite's WAL mode means hot copies are consistent without stopping the service.

Updates:

# Download new version
wget https://github.com/pocketbase/pocketbase/releases/latest/download/pocketbase_NEW_VERSION_linux_amd64.zip
unzip -o pocketbase_*_linux_amd64.zip -d /opt/pocketbase/

# Restart
sudo systemctl restart pocketbase
# PocketBase auto-migrates the database

Security:

  • Restrict admin UI access by IP if possible
  • Always use HTTPS (Caddy handles this)
  • Set proper API rules on all collections
  • Enable email verification for users

Performance:

  • PocketBase handles 10K+ concurrent connections
  • SQLite is surprisingly fast for reads
  • For write-heavy apps at scale, consider PostgreSQL-based alternatives

Resource Usage

UsersRAMCPUDisk
1-100128 MB1 core1 GB
100-1K256 MB1 core5 GB
1K-10K512 MB2 cores20 GB

PocketBase is the most lightweight backend you can deploy.

VPS Recommendations

ProviderSpecPrice
Hetzner2 vCPU, 2 GB RAM€4.50/month
DigitalOcean1 vCPU, 1 GB RAM$6/month
Linode1 vCPU, 1 GB RAM$5/month
Fly.io1 vCPU, 256 MBFree tier

Production Security Hardening

PocketBase's simplicity is a security asset — a smaller attack surface than multi-service deployments. But the admin UI at /_/ provides complete database access and must be protected. Follow the self-hosting security checklist and apply these PocketBase-specific measures.

UFW firewall: Block direct access to PocketBase's port; only Caddy handles external traffic.

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw deny 8090/tcp   # Block direct PocketBase access
sudo ufw enable

Fail2ban for SSH:

sudo apt install fail2ban -y

/etc/fail2ban/jail.local:

[sshd]
enabled = true
maxretry = 5
bantime = 3600
findtime = 600

Run PocketBase as a non-root user: The systemd service above specifies User=pocketbase. Create the dedicated user:

sudo useradd --system --no-create-home --shell /sbin/nologin pocketbase
sudo mkdir -p /opt/pocketbase
sudo chown -R pocketbase:pocketbase /opt/pocketbase

This ensures that even if PocketBase is compromised, the attacker cannot escalate to root privileges.

Restrict admin UI by IP: Use Caddy's remote_ip matcher to limit /_/ access to your own IP addresses:

api.yourdomain.com {
    @admin path /_/*
    handle @admin {
        @allowed remote_ip 1.2.3.4/32 5.6.7.8/32
        handle @allowed {
            reverse_proxy localhost:8090
        }
        respond 403
    }
    reverse_proxy localhost:8090
}

Disable SSH password auth:

PasswordAuthentication no
PermitRootLogin no

Restart: sudo systemctl restart sshd

Automatic security updates:

sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades

API rule discipline: Every PocketBase collection defaults to allowing all operations without authentication. Before going live, review every collection's API rules and apply the principle of least privilege — only grant the minimum access required. Collections with empty string rules ("") are completely public.

Troubleshooting Common Issues

Admin UI inaccessible at /_/

PocketBase's admin UI is served at /_/ — note the underscore, not /admin. If Caddy is blocking this path, verify no rewrite rules are interfering. Also check that the PocketBase service is running: sudo systemctl status pocketbase. Confirm it's listening on 8090: ss -tlnp | grep 8090.

"Failed to create admin" on first visit

This usually means PocketBase started correctly but the pb_data directory is not writable. Check: ls -la /opt/pocketbase/ — the pb_data directory should be owned by the pocketbase user. Fix with: sudo chown -R pocketbase:pocketbase /opt/pocketbase/pb_data.

Real-time subscriptions disconnect frequently

Real-time subscriptions use Server-Sent Events (SSE). Some reverse proxies and load balancers buffer SSE connections, causing apparent disconnections. In Caddy, this works correctly by default since Caddy supports streaming responses. If you're using Nginx or another proxy, ensure proxy_buffering off is set for SSE routes.

SQLite "database is locked" errors

This typically means multiple PocketBase processes are running simultaneously, both trying to write to the same data.db. Check: ps aux | grep pocketbase. If you see duplicate processes, stop all instances and restart the systemd service cleanly: sudo systemctl stop pocketbase && sudo systemctl start pocketbase. Enable WAL mode if not already active — PocketBase enables it by default, but verify with: sqlite3 pb_data/data.db "PRAGMA journal_mode;" (should return wal).

Files uploaded through the API not persisting after service restart

If files aren't in pb_data/storage/, they may have been uploaded to a different path. Verify the working directory in your systemd service matches where pb_data is expected. The WorkingDirectory in the service file should point to /opt/pocketbase. If files disappear after restart, the service may be writing to a temporary directory instead. Check systemctl cat pocketbase to confirm the working directory.

Slow queries on large collections

SQLite performs well for reads but can slow down with large datasets and complex filters. Add indexes to frequently-queried fields using PocketBase's admin UI: open a collection, go to Indexes tab, and add an index on the fields you filter by most often. For collections over 100K records, check query performance using SQLite's EXPLAIN QUERY PLAN directly on pb_data/data.db.

Ongoing Maintenance and Operations

PocketBase's operational simplicity is its defining characteristic. There's genuinely less to manage than any other backend in this guide — no container orchestration, no multi-service dependencies, no WAL configuration for multiple databases.

The backup routine is trivial. PocketBase stores everything — schema, data, auth, files — in the pb_data directory. A complete backup is cp -r /opt/pocketbase/pb_data /backups/pb_data_$(date +%Y%m%d). Schedule this daily with cron. For offsite redundancy, use restic to push the backup to Backblaze B2. The entire backup pipeline, including the offsite upload, typically completes in under 5 minutes for databases under 10 GB.

Updates are file swaps. Upgrading PocketBase is literally downloading a new binary and restarting the service. PocketBase handles database migrations automatically on startup — the new binary detects the current schema version and applies any needed migrations. There's no npm install, no docker pull, no migration command to run manually. This is an enormous operational advantage compared to every other tool in this guide.

Schema changes via migrations. PocketBase v0.21+ supports JavaScript migration files stored in pb_migrations/. Create a migration file to add a new collection field, change an index, or seed reference data. Migration files run automatically on startup, enabling a proper development workflow: create collection changes in the admin UI on a dev instance, export the migration, and apply it to production via deployment.

Custom hooks for business logic. PocketBase's pb_hooks directory supports JavaScript files that run on data events (create, update, delete) and HTTP routes. This is how you add business logic without modifying the Go binary. Example use cases: send a welcome email when a user registers, validate complex field constraints, trigger a webhook when an order is placed, or generate a thumbnail when an image is uploaded. The hooks runtime is synchronous and simple — ideal for lightweight server-side logic.

Monitoring the single process. With a single process to watch, monitoring is straightforward. A simple systemd RestartSec=5 handles crash recovery. For uptime monitoring, point Uptime Kuma at https://api.yourdomain.com/api/health — PocketBase returns 200 OK from this endpoint when healthy. Alert on non-200 responses.

When to migrate away. PocketBase's SQLite backbone is remarkably capable, but some workload patterns push past its limits. If you're seeing database is locked errors under production load, write throughput is exceeding SQLite's single-writer constraint. For apps that need more write concurrency — high-frequency event logging, real-time collaborative editing, financial transaction processing — migrate to a PostgreSQL-backed backend (Supabase self-hosted, or a standard PostgreSQL database with PostgREST). The migration is non-trivial but PocketBase's data export features make it manageable.

Community and long-term viability. PocketBase is maintained by a small team (primarily one developer) which is both a strength (opinionated, focused) and a risk (bus factor). The project has strong community momentum with 30K+ GitHub stars and active development, but it's worth watching how the project evolves. The self-hosted nature means your existing deployments continue working indefinitely regardless of the project's future — you're not dependent on a vendor staying in business.


Compare backend platforms on OSSAlt — features, scalability, and self-hosting options side by side.

See open source alternatives to PocketBase on OSSAlt.

The SaaS-to-Self-Hosted Migration Guide (Free PDF)

Step-by-step: infrastructure setup, data migration, backups, and security for 15+ common SaaS replacements. Used by 300+ developers.

Join 300+ self-hosters. Unsubscribe in one click.