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.
- Click New collection in the dashboard
- Name it (e.g., “posts”)
- Add fields — PocketBase supports text, number, bool, email, URL, date, file, relation, JSON, select, and GeoPoint fields
- Set API Rules to control who can read, create, update, and delete records
- Save — your REST API is immediately available at
/api/collections/posts/records
Key Features
| Feature | Details |
|---|---|
| Database | Embedded SQLite with WAL mode (concurrent reads, serialized writes) |
| Authentication | Password, OAuth2 (Google, GitHub, Microsoft, Apple, Discord, etc.), OTP, MFA |
| Realtime | Server-Sent Events for live data — subscribe to collection or record changes |
| File storage | Local filesystem (default) or S3-compatible (AWS, MinIO, Wasabi, DigitalOcean Spaces) |
| Admin dashboard | Full web UI for managing collections, records, settings, backups, and logs |
| REST API | CRUD with filtering, sorting, pagination, field selection, relation expansion, batch operations |
| API rules | Per-collection access control with filter expressions |
| JavaScript hooks | Extend PocketBase with JS files in pb_hooks/ |
| Auto HTTPS | Pass a domain to pocketbase serve example.com for automatic Let’s Encrypt |
| Backups | Built-in dashboard backup creates ZIP snapshots |
| Rate limiting | Built-in (v0.23.0+), configurable via dashboard |
Configuration
S3 File Storage
Switch from local filesystem to S3-compatible storage via the admin dashboard:
- Go to Settings → Files storage
- Select S3 and enter your credentials
- Existing local files are NOT migrated automatically
SMTP for Email
Configure email sending for password resets and verification:
- Go to Settings → Mail settings
- Enter your SMTP server details
- 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 Service | PocketBase Equivalent |
|---|---|
| Firebase Firestore | Collections + REST API |
| Firebase Auth | Built-in auth (password, OAuth2, OTP) |
| Firebase Storage | File fields + local or S3 storage |
| Firebase Realtime Database | SSE subscriptions |
| Supabase | Full backend (minus edge functions) |
| Airtable | Collections 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:
| Feature | PocketBase | Firebase | Supabase | Appwrite |
|---|---|---|---|---|
| Self-hosted | Yes (single binary) | No (Google Cloud only) | Yes (Docker, 12+ containers) | Yes (Docker, 6+ containers) |
| Database | SQLite (embedded) | Firestore (NoSQL) | PostgreSQL | MariaDB |
| Realtime | SSE | WebSocket | WebSocket (PostgreSQL LISTEN/NOTIFY) | WebSocket |
| Auth providers | Password, OAuth2, OTP, MFA | Password, OAuth2, phone, anonymous | Password, OAuth2, phone, magic link | Password, OAuth2, phone, magic link |
| File storage | Local or S3 | Google Cloud Storage | S3 | Local or S3 |
| Edge functions | No (JS hooks only) | Cloud Functions | Deno Edge Functions | Cloud Functions (Node.js) |
| Admin dashboard | Yes (built-in) | Firebase Console (cloud) | Yes (built-in) | Yes (built-in) |
| Horizontal scaling | No (single instance) | Automatic | Yes (PostgreSQL replication) | Yes (multiple containers) |
| Resource usage | ~15-30 MB RAM | N/A (managed) | ~2-4 GB RAM (12+ containers) | ~1-2 GB RAM (6+ containers) |
| Dependencies | None | N/A | PostgreSQL, GoTrue, PostgREST, Kong, etc. | Redis, MariaDB, ClamAV (optional) |
| Pricing (cloud) | Free (self-hosted only) | Generous free tier, then pay-per-use | Free tier, then $25/mo+ | Free tier, then $15/mo+ |
| Best for | Personal projects, internal tools, MVPs | Production apps needing Google ecosystem | Production apps needing PostgreSQL | Teams 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
LIKEqueries, 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 = idto 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 Settings → Application. 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:
| SDK | Install | Usage |
|---|---|---|
| JavaScript/TypeScript | npm install pocketbase | Browser, Node.js, Deno, Bun |
| Dart/Flutter | flutter pub add pocketbase | Mobile apps |
| Go | go get github.com/pocketbase/pocketbase | Go 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.
Related
- PocketBase vs Appwrite: Which Backend to Self-Host?
- PocketBase vs Supabase: Self-Hosted Backend Showdown
- Best Self-Hosted Low-Code Platforms
- Self-Hosted Alternatives to Firebase
- Self-Hosted Alternatives to Supabase
- NocoDB: Self-Hosted Airtable Alternative
- Appwrite: Self-Hosted Backend Platform
- Directus: Self-Hosted Headless CMS
- n8n: Self-Hosted Workflow Automation
- Docker Compose Basics
- Reverse Proxy Setup
- Backup Strategy
- Security Basics for Self-Hosting
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments