Self-Hosting PocketBase with Docker Compose

What Is PocketBase?

PocketBase is a self-hosted backend in a single binary. It bundles a SQLite database, realtime subscriptions, user authentication (password, OAuth2, OTP), file storage, and a full admin dashboard. You get a Firebase-like backend without vendor lock-in, external dependencies, or monthly bills. It’s written in Go, weighs about 15 MB, and can run on a Raspberry Pi. Official site.

Prerequisites

  • A Linux server (Ubuntu 22.04+ recommended)
  • Docker and Docker Compose installed (guide)
  • 256 MB of free RAM (PocketBase uses ~15-30 MB idle)
  • Understanding: PocketBase is pre-1.0 software (v0.36.x, 56k+ GitHub stars) — breaking changes can occur between versions
  • A registered domain name (recommended for production — PocketBase includes built-in HTTPS)

Docker Compose Configuration

PocketBase doesn’t have an official Docker image. The recommended approach is building from the official Dockerfile. Create a project directory:

mkdir pocketbase && cd pocketbase

Create a Dockerfile:

FROM alpine:3.21

ARG PB_VERSION=0.36.6

RUN apk add --no-cache \
    unzip \
    ca-certificates

ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_amd64.zip /tmp/pb.zip
RUN unzip /tmp/pb.zip -d /pb/ && \
    rm /tmp/pb.zip

EXPOSE 8090

CMD ["/pb/pocketbase", "serve", "--http=0.0.0.0:8090"]

Create docker-compose.yml:

services:
  pocketbase:
    build:
      context: .
      args:
        PB_VERSION: "0.36.6"
    container_name: pocketbase
    restart: unless-stopped
    ports:
      - "8090:8090"
    volumes:
      - pocketbase-data:/pb/pb_data       # SQLite database, uploaded files, backups
      # - ./pb_hooks:/pb/pb_hooks         # Uncomment for JavaScript hooks
      # - ./pb_migrations:/pb/pb_migrations # Uncomment for schema migrations
    environment:
      - GOMEMLIMIT=512MiB                 # Prevents OOM in constrained environments
    ulimits:
      nofile:
        soft: 4096
        hard: 4096

volumes:
  pocketbase-data:

Build and start:

docker compose up -d --build

Initial Setup

The admin dashboard is at http://your-server:8090/_/.

Create a superuser account via CLI (the recommended approach since v0.23):

docker exec -it pocketbase /pb/pocketbase superuser upsert [email protected] YourSecurePassword123

Log in to the dashboard with these credentials.

Create Your First Collection

Collections are PocketBase’s version of database tables.

  1. Click New collection in the dashboard
  2. Name it (e.g., “posts”)
  3. Add fields — PocketBase supports text, number, bool, email, URL, date, file, relation, JSON, select, and GeoPoint fields
  4. Set API Rules to control who can read, create, update, and delete records
  5. Save — your REST API is immediately available at /api/collections/posts/records

Key Features

FeatureDetails
DatabaseEmbedded SQLite with WAL mode (concurrent reads, serialized writes)
AuthenticationPassword, OAuth2 (Google, GitHub, Microsoft, Apple, Discord, etc.), OTP, MFA
RealtimeServer-Sent Events for live data — subscribe to collection or record changes
File storageLocal filesystem (default) or S3-compatible (AWS, MinIO, Wasabi, DigitalOcean Spaces)
Admin dashboardFull web UI for managing collections, records, settings, backups, and logs
REST APICRUD with filtering, sorting, pagination, field selection, relation expansion, batch operations
API rulesPer-collection access control with filter expressions
JavaScript hooksExtend PocketBase with JS files in pb_hooks/
Auto HTTPSPass a domain to pocketbase serve example.com for automatic Let’s Encrypt
BackupsBuilt-in dashboard backup creates ZIP snapshots
Rate limitingBuilt-in (v0.23.0+), configurable via dashboard

Configuration

S3 File Storage

Switch from local filesystem to S3-compatible storage via the admin dashboard:

  1. Go to SettingsFiles storage
  2. Select S3 and enter your credentials
  3. Existing local files are NOT migrated automatically

SMTP for Email

Configure email sending for password resets and verification:

  1. Go to SettingsMail settings
  2. Enter your SMTP server details
  3. Test with the built-in test button

Encryption

Encrypt sensitive settings (SMTP passwords, S3 keys) stored in the database:

environment:
  - PB_ENCRYPTION_KEY=your-random-32-character-string-here

Then start PocketBase with --encryptionEnv=PB_ENCRYPTION_KEY.

JavaScript Hooks

Extend PocketBase’s behavior with JavaScript files:

mkdir pb_hooks

Create pb_hooks/main.pb.js:

onRecordAfterCreateSuccess((e) => {
    console.log("New record created:", e.record.id)
}, "posts")

Mount the hooks directory in your Compose file and restart.

What PocketBase Replaces

Cloud ServicePocketBase Equivalent
Firebase FirestoreCollections + REST API
Firebase AuthBuilt-in auth (password, OAuth2, OTP)
Firebase StorageFile fields + local or S3 storage
Firebase Realtime DatabaseSSE subscriptions
SupabaseFull backend (minus edge functions)
AirtableCollections with the admin dashboard

PocketBase vs Firebase vs Supabase vs Appwrite

Choosing a backend-as-a-service means weighing simplicity against scalability. Here’s how PocketBase compares to the main alternatives:

FeaturePocketBaseFirebaseSupabaseAppwrite
Self-hostedYes (single binary)No (Google Cloud only)Yes (Docker, 12+ containers)Yes (Docker, 6+ containers)
DatabaseSQLite (embedded)Firestore (NoSQL)PostgreSQLMariaDB
RealtimeSSEWebSocketWebSocket (PostgreSQL LISTEN/NOTIFY)WebSocket
Auth providersPassword, OAuth2, OTP, MFAPassword, OAuth2, phone, anonymousPassword, OAuth2, phone, magic linkPassword, OAuth2, phone, magic link
File storageLocal or S3Google Cloud StorageS3Local or S3
Edge functionsNo (JS hooks only)Cloud FunctionsDeno Edge FunctionsCloud Functions (Node.js)
Admin dashboardYes (built-in)Firebase Console (cloud)Yes (built-in)Yes (built-in)
Horizontal scalingNo (single instance)AutomaticYes (PostgreSQL replication)Yes (multiple containers)
Resource usage~15-30 MB RAMN/A (managed)~2-4 GB RAM (12+ containers)~1-2 GB RAM (6+ containers)
DependenciesNoneN/APostgreSQL, GoTrue, PostgREST, Kong, etc.Redis, MariaDB, ClamAV (optional)
Pricing (cloud)Free (self-hosted only)Generous free tier, then pay-per-useFree tier, then $25/mo+Free tier, then $15/mo+
Best forPersonal projects, internal tools, MVPsProduction apps needing Google ecosystemProduction apps needing PostgreSQLTeams wanting full self-hosted stack

Bottom line: PocketBase wins on simplicity and resource efficiency. If your app fits in a single server and you don’t need horizontal scaling, nothing else comes close. Once you need multi-region deployment, connection pooling, or edge functions, move to Supabase or Appwrite.

Performance and Scaling Limits

PocketBase uses SQLite in WAL mode, which means:

  • Concurrent reads: Unlimited. Multiple API requests reading data execute in parallel.
  • Writes: Serialized. One write transaction at a time. This is the hard limit.
  • Practical throughput: ~500-1,000 simple write requests/second on modern hardware. Read-heavy workloads can handle thousands of concurrent users.
  • Database size: SQLite handles databases up to 281 TB. Practically, PocketBase works well up to ~50 GB before you notice query slowdowns on complex filters without proper indexes.

When PocketBase Is Enough

  • Personal projects and portfolios
  • Internal company tools (<100 concurrent users)
  • MVPs and prototypes (migrate to PostgreSQL-backed services later)
  • Mobile app backends with moderate write volume
  • IoT data collection (with batched writes)

When to Choose Something Else

  • Apps expecting >1,000 concurrent write-heavy users
  • Multi-region deployment requirements
  • Need for full-text search (PocketBase has basic LIKE queries, not Elasticsearch-level search)
  • Complex relational queries with many JOINs (SQLite handles them, but PostgreSQL optimizes better at scale)
  • Team needs edge functions or server-side compute beyond JS hooks

Security Hardening

Restrict Admin Dashboard Access

In production, limit dashboard access to your local network or VPN:

    ports:
      - "127.0.0.1:8090:8090"    # Bind to localhost only

Access the dashboard through an SSH tunnel or Tailscale:

ssh -L 8090:localhost:8090 your-server

Enable Settings Encryption

Encrypt sensitive configuration (SMTP passwords, S3 keys) stored in the SQLite database:

    environment:
      - PB_ENCRYPTION_KEY=your-random-32-character-string-here
    command: ["/pb/pocketbase", "serve", "--http=0.0.0.0:8090", "--encryptionEnv=PB_ENCRYPTION_KEY"]

Generate a key: openssl rand -hex 16.

API Rule Lockdown

By default, new collections have no API rules — meaning all CRUD operations are denied except for superusers. This is secure by default. When adding rules:

  • Use @request.auth.id != "" to require authentication
  • Use @request.auth.id = id to restrict users to their own records
  • Never set rules to empty string (allows public access) unless intentional
  • Test rules with the API preview in the dashboard before deploying

Rate Limiting

PocketBase includes built-in rate limiting (v0.23+). Configure in the admin dashboard under SettingsApplication. Default: 200 requests per 30 seconds per IP for API endpoints, 30 requests per 30 seconds for auth endpoints.

SDK and Client Libraries

PocketBase provides official SDKs that handle authentication, realtime subscriptions, and type safety:

SDKInstallUsage
JavaScript/TypeScriptnpm install pocketbaseBrowser, Node.js, Deno, Bun
Dart/Flutterflutter pub add pocketbaseMobile apps
Gogo get github.com/pocketbase/pocketbaseGo applications, custom extensions

Example — JavaScript client:

import PocketBase from 'pocketbase';

const pb = new PocketBase('https://your-pocketbase.example.com');

// Authenticate
await pb.collection('users').authWithPassword('[email protected]', 'password');

// Create a record
const record = await pb.collection('posts').create({
    title: 'Hello World',
    content: 'First post from the SDK',
});

// Subscribe to realtime changes
pb.collection('posts').subscribe('*', (e) => {
    console.log(e.action, e.record);
});

Community SDKs exist for Python, C#, Rust, and Kotlin — see the PocketBase docs for links.

Reverse Proxy

PocketBase serves everything on a single port. Standard reverse proxy configuration works:

location / {
    proxy_pass http://127.0.0.1:8090;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Connection '';
    proxy_buffering off;                # Required for SSE realtime
    client_max_body_size 50M;           # Adjust for file uploads
}

See Reverse Proxy Setup.

Backup

PocketBase stores everything in pb_data/:

# Use the built-in backup from the admin dashboard
# Or back up the volume directly:
docker compose stop pocketbase
docker run --rm -v pocketbase-data:/data -v $(pwd):/backup alpine \
  tar czf /backup/pocketbase-backup-$(date +%Y%m%d).tar.gz /data
docker compose start pocketbase

For databases over 2 GB, use sqlite3 .backup instead of the built-in ZIP backup — it’s faster and produces a consistent snapshot. See Backup Strategy.

Troubleshooting

”database is locked” errors

Symptom: Requests fail with SQLite busy/locked errors. Fix: Never store pb_data on network-attached storage (NFS, SMB, Azure Files). SQLite requires proper file locking that network filesystems don’t provide. Use local block storage or Docker named volumes backed by local disk.

Superuser creation token not visible

Symptom: First run prints a secure link to stderr, but you can’t see it in Docker. Fix: Check container logs with docker logs pocketbase. Or skip the link entirely and create a superuser via CLI: docker exec -it pocketbase /pb/pocketbase superuser upsert email password.

File uploads fail with large files

Symptom: Uploads over 5 MB fail. Fix: The default upload limit is 5 MB per file. Adjust in the admin dashboard under collection settings → file field → Max file size. Also increase client_max_body_size in your reverse proxy.

High memory usage under load

Symptom: Container memory grows beyond expected limits. Fix: Set GOMEMLIMIT=512MiB (or appropriate for your environment) to force Go’s garbage collector to be more aggressive. Also reduce log retention in Settings → Logs.

Container file descriptor limit

Symptom: Errors about “too many open files” with many realtime connections. Fix: Set ulimits.nofile in your Compose file to at least 4096. The default 1024 is insufficient for applications with many concurrent SSE connections.

Resource Requirements

  • RAM: ~15-30 MB idle, ~50-100 MB under load
  • CPU: Minimal — single core sufficient
  • Disk: 15 MB binary. Database and files grow with usage. 1 GB minimum recommended.
  • Limitation: Single-writer SQLite. Cannot horizontally scale — one instance only.

Verdict

PocketBase is the fastest way to self-host a backend. For personal projects, internal tools, and small-to-medium apps, it replaces Firebase, Supabase, and Airtable with zero monthly cost and zero external dependencies. The admin dashboard is polished, the API is well-designed, and the realtime support works out of the box.

The limitations are real: pre-1.0 software (expect breaking changes between minor versions), SQLite-only (no horizontal scaling), and no official Docker image. For production applications at scale, look at Appwrite or Supabase. For everything else — personal projects, internal tools, MVPs, mobile app backends — PocketBase is exceptional.

Frequently Asked Questions

Can PocketBase handle production traffic?

Yes, for single-server deployments. SQLite in WAL mode handles thousands of concurrent reads and ~500-1,000 writes/second. Many production apps run on PocketBase with 50-100 concurrent users without issues. The ceiling is horizontal scaling — you cannot run multiple PocketBase instances against the same database.

How do I upgrade PocketBase versions?

Change the PB_VERSION build arg in your Dockerfile and rebuild: docker compose up -d --build. PocketBase applies database migrations automatically on startup. Always back up pb_data/ before upgrading — downgrading after a migration is not supported.

Does PocketBase support PostgreSQL or MySQL?

No. SQLite is the only supported database and is deeply integrated into PocketBase’s architecture. This is intentional — it eliminates the need for a separate database container and simplifies deployment. If you need PostgreSQL, use Supabase or Appwrite.

Can I run PocketBase on a Raspberry Pi?

Yes. PocketBase uses ~15 MB RAM idle and the binary is only 15 MB. ARM64 builds are available for Pi 4 and Pi 5. Change the Dockerfile to download pocketbase_${PB_VERSION}_linux_arm64.zip. Performance is adequate for personal use with light workloads.

How do I migrate from Firebase to PocketBase?

Export your Firestore data as JSON, then import into PocketBase collections via the REST API or the admin dashboard. Auth users need to be recreated — PocketBase doesn’t import Firebase auth tokens. File storage can be migrated by downloading from Firebase Storage and uploading to PocketBase’s S3 backend or local storage. There’s no automated migration tool.

Is PocketBase suitable for mobile app backends?

Yes — it’s one of the best use cases. The official Dart/Flutter SDK handles auth, realtime subscriptions, and file uploads. The JavaScript SDK works for React Native. SSE-based realtime is battery-friendlier than WebSocket on mobile. The main limitation is that PocketBase runs on a single server, so plan for your expected user count.

What happens when PocketBase reaches v1.0?

The developer has indicated v1.0 will signal API stability — no more breaking changes between minor versions. Current v0.x releases occasionally change API behavior, collection schema format, or SDK interfaces. Pin your PocketBase version and test upgrades in a staging environment before production.

Can I extend PocketBase with custom logic?

Yes, two ways. JavaScript hooks (pb_hooks/ directory) let you add custom endpoints, modify request/response behavior, send emails, and run scheduled tasks without recompiling. Go extensions let you import PocketBase as a Go framework and add custom routes, middleware, and database logic — this gives full control but requires compiling a custom binary.

How do I set up PocketBase behind Cloudflare?

PocketBase works behind Cloudflare with one caveat: SSE realtime connections need Cloudflare’s “chunked transfer encoding” to work. Ensure your Cloudflare SSL mode is “Full (strict)” and disable Cloudflare’s response buffering for the PocketBase domain. If realtime drops after 100 seconds, Cloudflare’s proxy timeout is the cause — use Cloudflare Tunnel instead of proxying for persistent SSE connections.

How does PocketBase compare to Directus or Strapi?

PocketBase is a lightweight BaaS (backend-as-a-service) — a single binary with no dependencies. Directus and Strapi are headless CMS platforms designed for content management with richer editing UIs, workflows, and roles. Use PocketBase for app backends where you control the frontend. Use Directus/Strapi when non-technical users need to manage content through a polished editorial interface.

Comments