Pocketbase Self-Hosted Stack Documentation¶
Note: This documentation describes the Pocketbase stack template available via StackWiz. There are currently no active Pocketbase deployments in this infrastructure. Use
stackwiz --pocketbase <name>to create a new Pocketbase stack when needed.
Table of Contents¶
- Overview
- Architecture
- Why Pocketbase
- Stackwiz Integration
- Configuration Details
- Key Differences from Supabase
- When to Choose Pocketbase
- Backup Considerations
- Common Operations
- SDK Usage Examples
- Scaling Limitations
- Migration Strategies
Overview¶
Pocketbase is a lightweight Backend-as-a-Service (BaaS) solution that provides a complete backend infrastructure in a single executable file. Unlike traditional multi-service architectures, Pocketbase bundles everything needed for a modern application backend into one efficient Go binary.
In this infrastructure, Pocketbase serves as a lightweight alternative to Supabase, offering: - SQLite database with automatic migrations - Built-in authentication with JWT tokens - Real-time subscriptions via Server-Sent Events (SSE) - Auto-generated REST and realtime APIs - File storage with S3-compatible backend support - Admin dashboard for database and user management - JavaScript hooks for custom business logic - Type-safe SDKs for multiple languages
Each Pocketbase instance is exposed via a subdomain (e.g., myapp.rbnk.uk) with SSL termination handled by Traefik reverse proxy.
Architecture¶
Single Binary Architecture¶
Pocketbase's architecture is fundamentally different from traditional microservices-based backends:
┌─────────────────────────────────────────┐
│ Pocketbase Binary │
├─────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ Admin UI │ │ REST API │ │
│ └─────────────┘ └─────────────────┘ │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │Auth Service │ │ Realtime (SSE) │ │
│ └─────────────┘ └─────────────────┘ │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │File Storage │ │ JS Hooks Engine │ │
│ └─────────────┘ └─────────────────┘ │
├─────────────────────────────────────────┤
│ SQLite Database Engine │
└─────────────────────────────────────────┘
Key Components¶
- SQLite Database
- Embedded database with ACID compliance
- Automatic schema migrations
- Built-in backup capabilities
-
Support for JSON fields and full-text search
-
Authentication System
- JWT-based authentication
- OAuth2 provider support (Google, GitHub, etc.)
- Email/password authentication
- API key authentication
-
Session management
-
API Layer
- Auto-generated REST endpoints for all collections
- Real-time subscriptions via SSE
- Built-in pagination, filtering, and sorting
-
Field-level permissions
-
File Storage
- Local filesystem or S3-compatible storage
- Automatic image resizing
- Protected file serving
-
Direct upload URLs
-
Admin Dashboard
- Web-based UI at
/_/path - Collection and record management
- User management
- API logs and monitoring
- Settings configuration
Container Details¶
- Image:
ghcr.io/muchobien/pocketbase:latest - Base: Alpine Linux with Go runtime
- Memory Usage: ~30-50MB baseline
- CPU Usage: Minimal, event-driven architecture
- Storage: SQLite database + uploaded files
Why Pocketbase¶
Pocketbase was added to this infrastructure stack as a lightweight alternative to Supabase for several key reasons:
1. Resource Efficiency¶
- Single container vs. 6+ containers for Supabase
- ~50MB RAM usage vs. ~2GB for Supabase stack
- Minimal CPU overhead
- Perfect for resource-constrained environments
2. Operational Simplicity¶
- One binary to manage
- No complex inter-service networking
- Simple backup (copy SQLite file)
- Zero configuration for basic usage
3. Rapid Prototyping¶
- Instant setup with stackwiz
- No database schema design needed
- Auto-generated APIs
- Built-in admin UI
4. Edge Deployment¶
- Runs on Raspberry Pi, small VPS
- Can be embedded in desktop apps
- Suitable for offline-first applications
- Low latency, single-region deployments
5. Developer Experience¶
- Type-safe SDK generation
- JavaScript hooks for custom logic
- Simple migration system
- Excellent documentation
Stackwiz Integration¶
Pocketbase is fully integrated with the stackwiz deployment tool for automated setup:
Interactive Deployment¶
Non-Interactive Deployment¶
What Stackwiz Configures¶
-
Directory Structure
-
Network Configuration
- Connects to
traefik_proxynetwork -
Configures Traefik labels for SSL
-
Environment Setup
- Generates
.envfile with defaults - Sets memory limits
-
Configures ports
-
Security
- Sets file permissions (640 for .env)
- Placeholder for encryption key
- Health check configuration
Post-Deployment Steps¶
-
Generate Encryption Key (REQUIRED)
-
Start the Service
-
Create Admin Account
- Navigate to
https://myapp.rbnk.uk/_/ - Create first admin account
Configuration Details¶
Encryption Key Setup¶
The encryption key is crucial for securing auth tokens and sensitive data:
# .env file
PB_ENCRYPTION_KEY=your_32_character_key_here
# Key requirements:
# - Exactly 32 characters
# - Use random generation
# - Never commit to version control
# - Backup securely
Admin Dashboard Access¶
The admin dashboard is available at the /_/ path:
- URL:
https://your-domain.rbnk.uk/_/ - First Access: Create admin account
- Features:
- Collection management
- Record CRUD operations
- User management
- API logs
- Settings configuration
API Endpoints¶
Pocketbase automatically generates REST endpoints:
# Collections API
GET /api/collections/:collection/records
GET /api/collections/:collection/records/:id
POST /api/collections/:collection/records
PATCH /api/collections/:collection/records/:id
DELETE /api/collections/:collection/records/:id
# Auth endpoints
POST /api/collections/users/auth-with-password
POST /api/collections/users/request-password-reset
POST /api/collections/users/confirm-password-reset
POST /api/collections/users/request-verification
POST /api/collections/users/confirm-verification
# Realtime
GET /api/realtime (Server-Sent Events)
# Files
GET /api/files/:collection/:record/:filename
File Storage Configuration¶
Local storage (default):
volumes:
- ./pb_data:/pb_data # Contains uploaded files
- ./pb_public:/pb_public # Public static files
S3-compatible storage:
# .env configuration
S3_ENABLED=true
S3_ENDPOINT=https://s3.amazonaws.com
S3_BUCKET=your-bucket-name
S3_REGION=us-east-1
S3_ACCESS_KEY=your-access-key
S3_SECRET_KEY=your-secret-key
Memory Configuration¶
Adjust based on application needs:
# .env file
GOMEMLIMIT=256MiB # Small apps, <1000 users
GOMEMLIMIT=512MiB # Medium apps, default
GOMEMLIMIT=1GiB # Large apps, >10k users
GOMEMLIMIT=2GiB # Very large apps
Email Configuration¶
For sending transactional emails:
# .env file
SMTP_ENABLED=true
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USERNAME=[email protected]
SMTP_PASSWORD=your-app-specific-password
SMTP_TLS=true
Key Differences from Supabase¶
| Feature | Pocketbase | Supabase |
|---|---|---|
| Architecture | Single binary | Microservices (6+ containers) |
| Database | SQLite | PostgreSQL |
| Memory Usage | ~50MB | ~2GB |
| Setup Time | <1 minute | 5-10 minutes |
| Horizontal Scaling | No | Yes |
| SQL Features | Basic | Advanced (CTEs, views, functions) |
| Extensions | Limited | Many (PostGIS, pgvector, etc.) |
| Max Database Size | ~281TB (SQLite limit) | Unlimited |
| Concurrent Writes | Limited by SQLite | High concurrency |
| Real-time | SSE-based | PostgreSQL logical replication |
| Vector Search | No | Yes (pgvector) |
| Row Level Security | API-level | Database-level |
| Backup Complexity | Simple file copy | pg_dump + files |
| Multi-region | No | Yes |
| Connection Pooling | Built-in | PgBouncer |
Feature Comparison¶
Pocketbase Advantages: - Minimal resource usage - Simple deployment - Fast startup time - Easy backup/restore - Built-in admin UI - JavaScript hooks - Lower operational overhead
Supabase Advantages: - PostgreSQL power - Horizontal scaling - Complex queries - Database functions - Multiple schemas - Connection pooling - Enterprise features
When to Choose Pocketbase¶
Ideal Use Cases¶
- Prototypes and MVPs
- Rapid development
- Quick iteration
-
Low initial cost
-
Small to Medium Applications
- <10,000 active users
- <100 requests/second
-
<10GB data
-
Resource-Constrained Environments
- Raspberry Pi projects
- Small VPS (512MB RAM)
-
Edge deployments
-
Single-Region Applications
- Local business apps
- Internal tools
-
Regional services
-
Offline-First Applications
- Desktop apps with embedded backend
- Mobile apps with local sync
- Intermittent connectivity
Not Recommended For¶
- High-Concurrency Applications
- Many concurrent writes
- Complex transactions
-
Distributed systems
-
Data-Intensive Workloads
- Analytics queries
- Large datasets (>50GB)
-
Complex joins
-
Enterprise Requirements
- Audit logging
- Compliance needs
-
Multi-region deployment
-
PostgreSQL-Specific Features
- GIS applications (PostGIS)
- Vector search
- Custom extensions
Backup Considerations¶
SQLite-Specific Backup Strategy¶
Pocketbase uses SQLite, which requires special backup considerations:
-
Online Backup (Recommended)
-
File-Based Backup
-
Automated Backup Script
#!/bin/bash # Location: /srv/dockerdata/_scripts/pocketbase-backup.sh APP_NAME="myapp" BACKUP_DIR="/srv/backups/pocketbase/${APP_NAME}" mkdir -p "${BACKUP_DIR}" # Create backup docker exec ${APP_NAME} sqlite3 /pb_data/data.db ".backup /tmp/backup.db" docker cp ${APP_NAME}:/tmp/backup.db "${BACKUP_DIR}/db-$(date +%Y%m%d-%H%M%S).db" # Copy uploaded files tar -czf "${BACKUP_DIR}/files-$(date +%Y%m%d-%H%M%S).tar.gz" \ -C "/srv/dockerdata/${APP_NAME}" pb_data/storage # Cleanup old backups (keep 14 days) find "${BACKUP_DIR}" -name "*.db" -mtime +14 -delete find "${BACKUP_DIR}" -name "*.tar.gz" -mtime +14 -delete
Backup Best Practices¶
- Frequency
- Daily for production
- Before major updates
-
After schema changes
-
Testing
- Regular restore tests
- Verify data integrity
-
Check file attachments
-
Storage
- Local + remote copies
- Encrypted backups
- Version retention
Restore Process¶
# Stop the application
docker compose down
# Restore database
docker cp backup.db myapp:/pb_data/data.db
# Restore files
tar -xzf files-backup.tar.gz -C /srv/dockerdata/myapp/
# Restart
docker compose up -d
Common Operations¶
Admin Access¶
-
First-Time Setup
-
Admin API Access
# Login curl -X POST https://your-domain.rbnk.uk/api/admins/auth-with-password \ -H "Content-Type: application/json" \ -d '{"identity": "[email protected]", "password": "yourpassword"}'
API Usage¶
-
Authentication
# User login curl -X POST https://your-domain.rbnk.uk/api/collections/users/auth-with-password \ -H "Content-Type: application/json" \ -d '{"identity": "[email protected]", "password": "password"}' -
CRUD Operations
# Create record curl -X POST https://your-domain.rbnk.uk/api/collections/posts/records \ -H "Authorization: TOKEN" \ -H "Content-Type: application/json" \ -d '{"title": "Hello", "content": "World"}' # List records curl https://your-domain.rbnk.uk/api/collections/posts/records # Update record curl -X PATCH https://your-domain.rbnk.uk/api/collections/posts/records/RECORD_ID \ -H "Authorization: TOKEN" \ -d '{"title": "Updated"}'
Data Management¶
-
Export Data
-
Import Data
-
Schema Migrations
Container Management¶
# View logs
docker logs -f myapp
# Restart service
docker compose restart
# Update Pocketbase
docker compose pull
docker compose up -d
# Shell access
docker exec -it myapp /bin/sh
# Check health
curl http://localhost:8090/api/health
SDK Usage Examples¶
JavaScript/TypeScript¶
import PocketBase from 'pocketbase';
// Initialize client
const pb = new PocketBase('https://your-domain.rbnk.uk');
// Enable auto-cancellation
pb.autoCancellation(false);
// Authentication
const authData = await pb.collection('users').authWithPassword(
'[email protected]',
'password'
);
// CRUD operations
// Create
const record = await pb.collection('posts').create({
title: 'My Post',
content: 'Hello World',
author: pb.authStore.model.id
});
// Read
const post = await pb.collection('posts').getOne('RECORD_ID');
// Update
await pb.collection('posts').update('RECORD_ID', {
title: 'Updated Title'
});
// Delete
await pb.collection('posts').delete('RECORD_ID');
// List with filters
const posts = await pb.collection('posts').getList(1, 50, {
filter: 'created >= "2024-01-01"',
sort: '-created',
expand: 'author'
});
// Real-time subscriptions
pb.collection('posts').subscribe('*', (e) => {
console.log(e.action); // 'create', 'update', 'delete'
console.log(e.record); // the changed record
});
// File upload
const formData = new FormData();
formData.append('title', 'Post with image');
formData.append('image', fileInput.files[0]);
await pb.collection('posts').create(formData);
// Get file URL
const url = pb.files.getUrl(record, record.image);
React Hooks Example¶
import { useEffect, useState } from 'react';
import PocketBase from 'pocketbase';
const pb = new PocketBase('https://your-domain.rbnk.uk');
// Custom hook for real-time data
function useRealtimeCollection(collectionName) {
const [records, setRecords] = useState([]);
useEffect(() => {
// Initial fetch
pb.collection(collectionName).getList()
.then(result => setRecords(result.items));
// Subscribe to changes
const unsubscribe = pb.collection(collectionName).subscribe('*', (e) => {
if (e.action === 'create') {
setRecords(prev => [...prev, e.record]);
} else if (e.action === 'update') {
setRecords(prev => prev.map(r =>
r.id === e.record.id ? e.record : r
));
} else if (e.action === 'delete') {
setRecords(prev => prev.filter(r => r.id !== e.record.id));
}
});
return () => unsubscribe();
}, [collectionName]);
return records;
}
// Usage
function PostList() {
const posts = useRealtimeCollection('posts');
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
Python Example¶
import requests
from typing import Dict, List, Optional
class PocketBase:
def __init__(self, base_url: str):
self.base_url = base_url.rstrip('/')
self.token = None
def auth(self, email: str, password: str) -> Dict:
"""Authenticate user"""
response = requests.post(
f"{self.base_url}/api/collections/users/auth-with-password",
json={"identity": email, "password": password}
)
data = response.json()
self.token = data['token']
return data
def create(self, collection: str, data: Dict) -> Dict:
"""Create record"""
headers = {"Authorization": self.token} if self.token else {}
response = requests.post(
f"{self.base_url}/api/collections/{collection}/records",
json=data,
headers=headers
)
return response.json()
def get_list(self, collection: str, page: int = 1,
per_page: int = 30, filter: str = "") -> Dict:
"""List records"""
params = {"page": page, "perPage": per_page}
if filter:
params["filter"] = filter
response = requests.get(
f"{self.base_url}/api/collections/{collection}/records",
params=params
)
return response.json()
# Usage
pb = PocketBase("https://your-domain.rbnk.uk")
pb.auth("[email protected]", "password")
# Create post
post = pb.create("posts", {
"title": "Python Post",
"content": "Created from Python"
})
# List posts
posts = pb.get_list("posts", filter='title ~ "Python"')
Go Example¶
package main
import (
"github.com/pocketbase/pocketbase/tools/auth"
"github.com/pocketbase/pocketbase/core"
)
// Custom hook example
func main() {
app := pocketbase.New()
// Hook into record creation
app.OnRecordBeforeCreateRequest("posts").Add(func(e *core.RecordCreateEvent) error {
// Auto-set author
if e.HttpContext.Get("authRecord") != nil {
user := e.HttpContext.Get("authRecord").(*models.Record)
e.Record.Set("author", user.Id)
}
return nil
})
// Custom endpoint
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
e.Router.GET("/api/custom/stats", func(c echo.Context) error {
// Custom logic here
return c.JSON(200, map[string]interface{}{
"total_posts": 100,
"total_users": 50,
})
})
return nil
})
app.Start()
}
Scaling Limitations¶
SQLite Limitations¶
- Write Concurrency
- Single writer at a time
- Readers don't block writers (WAL mode)
-
~100-200 writes/second typical
-
Database Size
- Theoretical: 281TB
- Practical: 10-50GB recommended
-
Performance degrades with size
-
Connection Limits
- No connection pooling needed
- Embedded database
- Limited by file handles
Performance Boundaries¶
- User Capacity
- Comfortable: <10,000 active users
- Possible: <50,000 users
-
Not recommended: >100,000 users
-
Request Throughput
- Read-heavy: 1000+ req/sec
- Write-heavy: 100-200 req/sec
-
Mixed load: 300-500 req/sec
-
Data Volume
- Optimal: <1GB
- Good: <10GB
- Challenging: >50GB
Scaling Strategies¶
-
Vertical Scaling
-
Caching
- Use CDN for static files
- Cache API responses
-
Client-side caching
-
Read Replicas
-
Sharding
- Split by tenant
- Geographic sharding
- Feature-based splits
When to Migrate¶
Consider migrating to Supabase when: - Consistent >200 writes/second - Database >50GB - Need horizontal scaling - Require PostgreSQL features - Multi-region deployment
Migration Strategies¶
From Pocketbase to Supabase¶
-
Schema Migration
-- Pocketbase collections → PostgreSQL tables CREATE TABLE posts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title TEXT NOT NULL, content TEXT, author UUID REFERENCES auth.users(id), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- Add RLS policies ALTER TABLE posts ENABLE ROW LEVEL SECURITY; CREATE POLICY "Public read" ON posts FOR SELECT USING (true); CREATE POLICY "Author write" ON posts FOR ALL USING (auth.uid() = author); -
Data Export/Import
-
Code Migration
From Supabase to Pocketbase¶
-
Schema Simplification
-
Data Migration
-
Feature Adaptation
- PostgreSQL functions → JS hooks
- RLS policies → API rules
- Views → Client-side filtering
Migration Tools¶
-
pb-migrate
-
Data Sync Script
// Bidirectional sync between Pocketbase and Supabase const syncData = async () => { // Fetch from Pocketbase const pbRecords = await pb.collection('posts').getFullList(); // Sync to Supabase for (const record of pbRecords) { await supabase.from('posts').upsert({ id: record.id, title: record.title, content: record.content, updated_at: record.updated }); } };
Best Practices¶
- Gradual Migration
- Run both systems in parallel
- Migrate feature by feature
-
Validate data integrity
-
Testing Strategy
- Compare API responses
- Load test both systems
-
User acceptance testing
-
Rollback Plan
- Keep backups of both systems
- Document migration steps
- Test rollback procedure
Summary¶
Pocketbase provides an excellent lightweight alternative to Supabase for applications that don't require the full power of PostgreSQL or horizontal scaling. Its single-binary architecture, minimal resource usage, and built-in features make it ideal for prototypes, small to medium applications, and resource-constrained environments.
Key takeaways: - Use Pocketbase for rapid development, small apps, and when simplicity is paramount - Use Supabase for complex queries, large datasets, and enterprise requirements - Migration is possible in both directions with proper planning - Backup regularly using SQLite-specific strategies - Monitor performance and plan for growth
With stackwiz integration, deploying Pocketbase is as simple as running a single command, making it an excellent choice for quickly spinning up new backend services in this infrastructure.