PocketBase: Full Backend as a Single Go Binary
HOW TO GUIDES March 28, 2026, 11:30 p.m.

PocketBase: Full Backend as a Single Go Binary

PocketBase has taken the developer community by storm because it delivers a full‑featured backend with a single, tiny Go binary. No Docker, no separate database server, and no cloud‑only lock‑in – just download, run, and start building. In this article we’ll walk through the core concepts, spin up a real‑world Todo API, and explore advanced tricks that keep your stack lean yet powerful.

What is PocketBase?

PocketBase is an open‑source backend‑as‑a‑service (BaaS) that bundles a SQLite database, an HTTP API, real‑time subscriptions, authentication, and a web admin UI into one 10 MB executable written in Go. Because it compiles to a single binary, deployment becomes as easy as copying a file to any Linux, macOS, or Windows host.

Under the hood PocketBase uses SQLite for persistence, which means you get ACID guarantees without managing a separate DBMS. The API follows conventional REST patterns, and the real‑time layer speaks WebSockets, letting front‑ends react instantly to data changes.

Getting Started

Installation

Grab the latest release from the GitHub releases page, unzip, and make the binary executable. On macOS or Linux you can also use Homebrew:

brew install pocketbase

Windows users can drop the .exe into a folder that’s on the system PATH for easy access from any command prompt.

Running the Binary

Start the server with a single command. By default PocketBase listens on 127.0.0.1:8090 and creates a pb_data folder in the current directory to store the SQLite file and uploaded assets.

pocketbase serve

If you need to bind to a different address or port, use the --http flag:

pocketbase serve --http 0.0.0.0:8080

Once the server is up, navigate to http://localhost:8090/_/ to see the sleek admin dashboard where you can create collections, manage users, and inspect data.

Core Concepts

Collections

Collections are the building blocks of your data model – think of them as tables in a relational database. Each collection can have any number of fields, including text, numbers, booleans, dates, and file uploads. PocketBase also supports relational fields, allowing you to link records across collections without writing joins.

All collection definitions are stored as JSON inside the SQLite file, which means you can version‑control them by exporting the schema and checking it into Git.

Authentication

PocketBase ships with a ready‑made authentication system that supports email/password, OAuth2 (Google, GitHub, etc.), and even anonymous users. The /api/users endpoint lets you register, login, and manage password resets out of the box.

Every request that needs protection must include the Authorization: Bearer <token> header. Tokens are JWT‑like strings signed by the server, and they expire after a configurable period (default 24 h).

File Storage

File fields are stored on disk in the pb_data/files directory, and PocketBase serves them through signed URLs that respect the same auth rules as the rest of the API. This makes it trivial to attach images, PDFs, or any binary blob to a record.

Because files are served via a dedicated route, you can offload them to a CDN by configuring a reverse proxy – the binary itself never needs to know about the external storage.

Building a Simple Todo API

Let’s create a minimal Todo application that demonstrates CRUD operations, authentication, and real‑time updates. We’ll start by defining a todos collection via the admin UI with three fields: title (text), completed (boolean), and owner (relation to the built‑in users collection).

After saving the collection, the API automatically exposes the following endpoints:

  • GET /api/collections/todos/records – list todos
  • POST /api/collections/todos/records – create a new todo
  • PATCH /api/collections/todos/records/:id – update a todo
  • DELETE /api/collections/todos/records/:id – delete a todo

Now let’s interact with this API from Python. We’ll use the requests library to register a user, log in, and perform CRUD actions.

import requests

BASE_URL = "http://localhost:8090"

# 1️⃣ Register a new user
def register(email, password):
    payload = {"email": email, "password": password, "passwordConfirm": password}
    r = requests.post(f"{BASE_URL}/api/users", json=payload)
    r.raise_for_status()
    print("User registered:", r.json()["id"])

# 2️⃣ Log in and obtain a JWT token
def login(email, password):
    payload = {"identity": email, "password": password}
    r = requests.post(f"{BASE_URL}/api/users/auth-via-email", json=payload)
    r.raise_for_status()
    token = r.json()["token"]
    print("Logged in, token length:", len(token))
    return token

# 3️⃣ Create a todo for the authenticated user
def create_todo(token, title):
    headers = {"Authorization": f"Bearer {token}"}
    payload = {"title": title, "completed": False}
    r = requests.post(f"{BASE_URL}/api/collections/todos/records", json=payload, headers=headers)
    r.raise_for_status()
    print("Todo created:", r.json()["id"])
    return r.json()["id"]

# Example workflow
if __name__ == "__main__":
    email, pwd = "alice@example.com", "SuperSecret123"
    register(email, pwd)
    jwt = login(email, pwd)
    todo_id = create_todo(jwt, "Write PocketBase blog post")

The code above demonstrates the entire lifecycle: a fresh user registers, logs in to receive a token, and then creates a Todo record that is automatically linked to the user via the owner relation.

Reading, Updating, and Deleting

Fetching the list of todos is a simple GET request with the same Authorization header. Updating a record uses PATCH, and you can toggle the completed flag in one line.

def list_todos(token):
    headers = {"Authorization": f"Bearer {token}"}
    r = requests.get(f"{BASE_URL}/api/collections/todos/records", headers=headers)
    r.raise_for_status()
    return r.json()["items"]

def toggle_completed(token, todo_id, completed):
    headers = {"Authorization": f"Bearer {token}"}
    payload = {"completed": completed}
    r = requests.patch(f"{BASE_URL}/api/collections/todos/records/{todo_id}", json=payload, headers=headers)
    r.raise_for_status()
    print("Todo updated:", r.json()["id"])

def delete_todo(token, todo_id):
    headers = {"Authorization": f"Bearer {token}"}
    r = requests.delete(f"{BASE_URL}/api/collections/todos/records/{todo_id}", headers=headers)
    r.raise_for_status()
    print("Todo deleted:", todo_id)

Because PocketBase respects the owner relation, a user can only see, edit, or delete their own todos unless you explicitly grant broader permissions in the collection’s rules.

Real‑Time Subscriptions

One of PocketBase’s standout features is built‑in real‑time updates via WebSockets. Clients can subscribe to a collection and receive events whenever a record is created, updated, or deleted. This eliminates the need for polling and makes collaborative apps feel instant.

import websocket
import json

def on_message(ws, message):
    event = json.loads(message)
    print("Realtime event:", event["action"], "on", event["record"]["id"])

def subscribe(token):
    ws_url = f"ws://localhost:8090/api/realtime?token={token}"
    ws = websocket.WebSocketApp(ws_url, on_message=on_message)
    ws.run_forever()

# Run in a separate thread or process
# subscribe(jwt)

The WebSocket URL includes the JWT token, so the server knows which user’s scope to apply. The payload contains the action (create, update, delete) and the full record data, enabling you to update UI components instantly.

Real‑World Use Cases

PocketBase shines in scenarios where you need a full backend but want to avoid the operational overhead of a traditional stack. Below are three common patterns where developers have adopted PocketBase with great success.

  • Mobile MVPs – Rapidly prototype iOS/Android apps with a single binary handling sync, auth, and file uploads. The tiny footprint means you can even bundle PocketBase inside a native container for offline‑first experiences.
  • Internal Tools – Build admin panels, dashboards, or CRUD interfaces for internal teams. The built‑in admin UI doubles as a low‑code editor, reducing the need for separate admin backends.
  • Edge‑Enabled Services – Deploy PocketBase to edge locations (e.g., Fly.io, Cloudflare Workers with a custom build) to serve low‑latency APIs close to your users without a multi‑service architecture.

Because the binary runs anywhere a Go runtime is available, you can host it on a cheap VPS, a Kubernetes pod, or even a Raspberry Pi for IoT projects.

Advanced Features

Hooks & Custom Logic

PocketBase allows you to write Go plugins that run before or after any CRUD operation. These hooks are perfect for enforcing business rules, sending notifications, or integrating with third‑party APIs.

To add a hook, create a Go file in the pb_hooks directory and register it in the main.go before calling pb.Start(). Here’s a minimal example that auto‑assigns the current user as the owner of a new Todo:

package main

import (
    "github.com/pocketbase/pocketbase"
    "github.com/pocketbase/pocketbase/models"
)

func main() {
    app := pocketbase.New()

    // Hook runs before a new record is created in the "todos" collection
    app.OnRecordBeforeCreate("todos", func(e *pocketbase.RecordCreateEvent) error {
        // e.Record is the new record about to be saved
        // e.AuthRecord contains the authenticated user (if any)
        if e.AuthRecord != nil {
            e.Record.Set("owner", e.AuthRecord.Id)
        }
        return nil
    })

    app.Start()
}

Compile the binary again (`go build -o pocketbase`) and the hook will be active on every create request, guaranteeing data integrity without extra client‑side code.

Admin Dashboard Customization

The admin UI is built with Vue.js and can be extended via custom pages or injected CSS. Place a custom.css file inside pb_data/public and the dashboard will automatically load it, letting you brand the interface to match your organization’s look and feel.

For deeper integrations, you can add a new route that serves a Vue component, effectively turning PocketBase into a low‑code platform where non‑developers can build internal tools without touching the core binary.

Pro Tips

Tip 1: Keep your pb_data folder on a separate volume or mount it as a Docker volume. This ensures data persists across binary upgrades or container restarts.

Tip 2: Use collection rules (written in a simple expression language) to enforce row‑level security. For example, owner.id = @request.auth.id guarantees users can only see their own records.

Tip 3: Enable --dir flag to point PocketBase to a read‑only assets directory while storing the SQLite file elsewhere. This separation improves backup strategies and allows CDN offloading for static files.

Tip 4: When scaling, run multiple PocketBase instances behind a load balancer and share the same SQLite file via a network file system (NFS) or use the upcoming PostgreSQL adapter for true multi‑node consistency.

Deployment Strategies

Because PocketBase is just a binary, you have a lot of flexibility in how you ship it. Below are three popular approaches, each with its own trade‑offs.

  1. Docker Container – Write a tiny Dockerfile that copies the binary and sets the entrypoint. This is ideal for CI/CD pipelines and works seamlessly with Kubernetes or Docker Compose.
  2. Standalone Binary on a VPS – Download the binary directly onto a cloud VM, configure a systemd service, and let Nginx handle TLS termination. This yields the lowest overhead and fastest cold start.
  3. Edge Deployments – Build a custom Go binary with GOOS=linux GOARCH=arm64 and deploy to edge providers like Fly.io or Render. The small size (<10 MB) ensures rapid propagation across edge nodes.

Regardless of the method, remember to set the PB_ADMIN_EMAIL

Share this article