Skip to content

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

  1. Overview
  2. Architecture
  3. Why Pocketbase
  4. Stackwiz Integration
  5. Configuration Details
  6. Key Differences from Supabase
  7. When to Choose Pocketbase
  8. Backup Considerations
  9. Common Operations
  10. SDK Usage Examples
  11. Scaling Limitations
  12. 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

  1. SQLite Database
  2. Embedded database with ACID compliance
  3. Automatic schema migrations
  4. Built-in backup capabilities
  5. Support for JSON fields and full-text search

  6. Authentication System

  7. JWT-based authentication
  8. OAuth2 provider support (Google, GitHub, etc.)
  9. Email/password authentication
  10. API key authentication
  11. Session management

  12. API Layer

  13. Auto-generated REST endpoints for all collections
  14. Real-time subscriptions via SSE
  15. Built-in pagination, filtering, and sorting
  16. Field-level permissions

  17. File Storage

  18. Local filesystem or S3-compatible storage
  19. Automatic image resizing
  20. Protected file serving
  21. Direct upload URLs

  22. Admin Dashboard

  23. Web-based UI at /_/ path
  24. Collection and record management
  25. User management
  26. API logs and monitoring
  27. 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

stackwiz
# Select option 2 for Pocketbase
# Follow prompts for app name and domain

Non-Interactive Deployment

stackwiz -n myapp -d myapp.rbnk.uk --type pocketbase --yes

What Stackwiz Configures

  1. Directory Structure

    /srv/dockerdata/myapp/
    ├── docker-compose.yml    # Docker configuration
    ├── .env                  # Environment variables
    ├── pb_data/             # Database and config
    ├── pb_public/           # Static file serving
    ├── pb_migrations/       # Schema migrations
    └── pb_hooks/            # JavaScript hooks
    

  2. Network Configuration

  3. Connects to traefik_proxy network
  4. Configures Traefik labels for SSL

  5. Environment Setup

  6. Generates .env file with defaults
  7. Sets memory limits
  8. Configures ports

  9. Security

  10. Sets file permissions (640 for .env)
  11. Placeholder for encryption key
  12. Health check configuration

Post-Deployment Steps

  1. Generate Encryption Key (REQUIRED)

    # Generate secure key
    openssl rand -hex 16
    
    # Update .env file
    cd /srv/dockerdata/myapp
    nano .env
    # Replace PB_ENCRYPTION_KEY value
    

  2. Start the Service

    docker compose up -d
    

  3. Create Admin Account

  4. Navigate to https://myapp.rbnk.uk/_/
  5. 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

  1. Prototypes and MVPs
  2. Rapid development
  3. Quick iteration
  4. Low initial cost

  5. Small to Medium Applications

  6. <10,000 active users
  7. <100 requests/second
  8. <10GB data

  9. Resource-Constrained Environments

  10. Raspberry Pi projects
  11. Small VPS (512MB RAM)
  12. Edge deployments

  13. Single-Region Applications

  14. Local business apps
  15. Internal tools
  16. Regional services

  17. Offline-First Applications

  18. Desktop apps with embedded backend
  19. Mobile apps with local sync
  20. Intermittent connectivity
  1. High-Concurrency Applications
  2. Many concurrent writes
  3. Complex transactions
  4. Distributed systems

  5. Data-Intensive Workloads

  6. Analytics queries
  7. Large datasets (>50GB)
  8. Complex joins

  9. Enterprise Requirements

  10. Audit logging
  11. Compliance needs
  12. Multi-region deployment

  13. PostgreSQL-Specific Features

  14. GIS applications (PostGIS)
  15. Vector search
  16. Custom extensions

Backup Considerations

SQLite-Specific Backup Strategy

Pocketbase uses SQLite, which requires special backup considerations:

  1. Online Backup (Recommended)

    # Using Pocketbase's built-in backup
    curl -X POST https://your-domain.rbnk.uk/api/backups \
      -H "Authorization: TOKEN"
    

  2. File-Based Backup

    # Stop writes temporarily
    docker exec myapp sqlite3 /pb_data/data.db '.backup /pb_data/backup.db'
    
    # Copy backup file
    docker cp myapp:/pb_data/backup.db ./backup-$(date +%Y%m%d).db
    

  3. 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

  1. Frequency
  2. Daily for production
  3. Before major updates
  4. After schema changes

  5. Testing

  6. Regular restore tests
  7. Verify data integrity
  8. Check file attachments

  9. Storage

  10. Local + remote copies
  11. Encrypted backups
  12. 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

  1. First-Time Setup

    # Navigate to admin UI
    https://your-domain.rbnk.uk/_/
    
    # Create admin account
    # Set strong password
    

  2. 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

  1. 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"}'
    

  2. 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

  1. Export Data

    # Export as JSON
    curl https://your-domain.rbnk.uk/api/collections/posts/records?perPage=500 \
      -H "Authorization: TOKEN" > posts.json
    

  2. Import Data

    # Via admin UI
    # Navigate to Collections > Import
    
    # Via API (batch create)
    for record in records.json; do
      curl -X POST .../api/collections/posts/records -d @record
    done
    

  3. Schema Migrations

    // Place in pb_migrations/
    migrate((db) => {
      // Create new collection
      const collection = new Collection({
        name: "products",
        type: "base",
        schema: [
          {
            name: "name",
            type: "text",
            required: true,
          },
          {
            name: "price",
            type: "number",
            min: 0,
          }
        ]
      });
    
      return Dao(db).saveCollection(collection);
    });
    

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

  1. Write Concurrency
  2. Single writer at a time
  3. Readers don't block writers (WAL mode)
  4. ~100-200 writes/second typical

  5. Database Size

  6. Theoretical: 281TB
  7. Practical: 10-50GB recommended
  8. Performance degrades with size

  9. Connection Limits

  10. No connection pooling needed
  11. Embedded database
  12. Limited by file handles

Performance Boundaries

  1. User Capacity
  2. Comfortable: <10,000 active users
  3. Possible: <50,000 users
  4. Not recommended: >100,000 users

  5. Request Throughput

  6. Read-heavy: 1000+ req/sec
  7. Write-heavy: 100-200 req/sec
  8. Mixed load: 300-500 req/sec

  9. Data Volume

  10. Optimal: <1GB
  11. Good: <10GB
  12. Challenging: >50GB

Scaling Strategies

  1. Vertical Scaling

    # Increase memory limit
    GOMEMLIMIT=2GiB
    
    # Use faster storage (NVMe SSD)
    # Enable WAL mode (default)
    

  2. Caching

  3. Use CDN for static files
  4. Cache API responses
  5. Client-side caching

  6. Read Replicas

    # Create read-only copies
    sqlite3 data.db ".backup readonly.db"
    
    # Serve read traffic separately
    

  7. Sharding

  8. Split by tenant
  9. Geographic sharding
  10. 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

  1. 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);
    

  2. Data Export/Import

    # Export from Pocketbase
    curl https://pb.example.com/api/collections/posts/records?perPage=500 \
      -H "Authorization: TOKEN" > posts.json
    
    # Transform and import to Supabase
    node migrate-to-supabase.js posts.json
    

  3. Code Migration

    // Pocketbase
    const pb = new PocketBase('https://pb.example.com');
    await pb.collection('posts').create({...});
    
    // Supabase
    const supabase = createClient(url, key);
    await supabase.from('posts').insert({...});
    

From Supabase to Pocketbase

  1. Schema Simplification

    // Create Pocketbase collections
    const collection = {
      name: "posts",
      type: "base",
      schema: [
        { name: "title", type: "text", required: true },
        { name: "content", type: "text" },
        { name: "author", type: "relation", collectionId: "users" }
      ]
    };
    

  2. Data Migration

    # Export from Supabase
    pg_dump --data-only --table=posts > posts.sql
    
    # Convert to Pocketbase format
    python supabase-to-pb.py posts.sql
    

  3. Feature Adaptation

  4. PostgreSQL functions → JS hooks
  5. RLS policies → API rules
  6. Views → Client-side filtering

Migration Tools

  1. pb-migrate

    # Install
    npm install -g pb-migrate
    
    # Export schema
    pb-migrate export --url https://pb.example.com --admin-token TOKEN
    
    # Import to new instance
    pb-migrate import --url https://new-pb.example.com --file schema.json
    

  2. 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

  1. Gradual Migration
  2. Run both systems in parallel
  3. Migrate feature by feature
  4. Validate data integrity

  5. Testing Strategy

  6. Compare API responses
  7. Load test both systems
  8. User acceptance testing

  9. Rollback Plan

  10. Keep backups of both systems
  11. Document migration steps
  12. 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.