DEV Community

Cover image for Deploying Self-Hosted PocketBase on Cloud Servers
Raizan
Raizan

Posted on Originally published at chasebot.online

Deploying Self-Hosted PocketBase on Cloud Servers

What You'll Need

Before launching your production PocketBase instance, you will need the following resources:

  • A Linux virtual private server. I recommend Hetzner VPS or Contabo VPS for high performance at a low cost, or DigitalOcean for rapid provisioning.
  • A custom domain name managed via Namecheap with DNS access to map your primary domain or subdomain to your server's public IP address.
  • Root or sudo privileges on an active Ubuntu 22.04 or Ubuntu 24.04 LTS server.
  • Optional: An n8n Cloud instance if you plan to link PocketBase real-time events to external backend processing pipelines.

Table of Contents

Provisioning and Firewall Setup on Your Server

I love light footprint tools. PocketBase packs an entire real-time database, authentication engine, file storage manager, and admin dashboard into a single compiled Go binary. Because it runs on top of an embedded SQLite engine, you do not need to install complex database clusters like PostgreSQL or MySQL. A single lightweight cloud instance on Hetzner VPS can easily serve millions of API requests per month for a fraction of traditional server costs.

Start by accessing your server via SSH. I recommend creating a dedicated non-root user account to isolate your application binaries from standard system administrative tasks. Execute the following commands to create a service user and grant necessary system privileges:

adduser --disabled-password --gecos "" pocketbase
usermod -aG sudo pocketbase
Enter fullscreen mode Exit fullscreen mode

Next, configure strict network filtering rules using Uncomplicated Firewall (UFW). PocketBase default configuration runs on local port 8090, but we should strictly block external exposure to this port. Only standard web traffic ports (80 and 443) alongside SSH (22) should remain exposed to public interfaces.

ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
Enter fullscreen mode Exit fullscreen mode

To verify your security baseline, list all active rules to confirm port 8090 is completely hidden from incoming public internet connections:

ufw status verbose
Enter fullscreen mode Exit fullscreen mode

Your terminal output must explicitly confirm that traffic on port 22, 80, and 443 is ALLOWED from anywhere, while all other inbound ports default to DENY.

Installing and Configuring PocketBase as a Systemd Service

With our firewall configured, we can download the official Linux binary and place it in a proper execution folder structure. We will install PocketBase inside /usr/local/bin and create a dedicated storage directory inside /var/lib/pocketbase to house SQLite database files and user-uploaded media.

Run these steps as root or with sudo elevated privileges:

cd /tmp
wget https://github.com/pocketbase/pocketbase/releases/download/v0.22.21/pocketbase_0.22.21_linux_amd64.zip
apt-get update && apt-get install -y unzip
unzip pocketbase_0.22.21_linux_amd64.zip -d /usr/local/bin/
chmod +x /usr/local/bin/pocketbase
rm pocketbase_0.22.21_linux_amd64.zip
Enter fullscreen mode Exit fullscreen mode

Now build the production storage directory and adjust folder ownership so our isolated user owns the entire runtime folder:

mkdir -p /var/lib/pocketbase/pb_data
mkdir -p /var/lib/pocketbase/pb_hooks
mkdir -p /var/lib/pocketbase/pb_public
chown -R pocketbase:pocketbase /var/lib/pocketbase
Enter fullscreen mode Exit fullscreen mode

Running PocketBase manually inside a screen or tmux session is risky because the process will fail to restart automatically if your instance reboots or experiences an unhandled memory exception. We must build a robust systemd service configuration file.

Create the service file using your preferred terminal text editor:

nano /etc/systemd/system/pocketbase.service
Enter fullscreen mode Exit fullscreen mode

Insert the following full configuration into the systemd service file:

[Unit]
Description=PocketBase Realtime Backend Service
After=network.target

[Service]
Type=simple
User=pocketbase
Group=pocketbase
WorkingDirectory=/var/lib/pocketbase
ExecStart=/usr/local/bin/pocketbase serve --dir=/var/lib/pocketbase/pb_data --hooksDir=/var/lib/pocketbase/pb_hooks --publicDir=/var/lib/pocketbase/pb_public --http=127.0.0.1:8090
Restart=always
RestartSec=5s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=pocketbase

LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

Save the file and exit the editor. Reload systemd daemon definitions, then enable and boot your service:

systemctl daemon-reload
systemctl enable pocketbase
systemctl start pocketbase
Enter fullscreen mode Exit fullscreen mode

Check the active execution status of your application with this command:

systemctl status pocketbase
Enter fullscreen mode Exit fullscreen mode

If configured properly, you will see an active (running) status log showing that PocketBase is bound to address 127.0.0.1:8090.

💡 Fast-Track Your Project: Don't want to configure this yourself? I build custom n8n pipelines and bots. Message me with code SYS3-DEVTO.

Setting Up Caddy as a Reverse Proxy with Automatic SSL

Now that the core service is isolated locally on port 8090, we need a web server to handle public domain resolution, manage Let's Encrypt TLS certificate generation, and proxy requests down to PocketBase. Caddy is my preferred tool for this step because it automatically manages SSL renewal cycles with zero manual intervention.

To install Caddy on Ubuntu, run these official package repository commands:

apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt-get update
apt-get install -y caddy
Enter fullscreen mode Exit fullscreen mode

Before updating Caddy's configuration file, log into your domain manager at Namecheap or your preferred DNS provider. Create an A record pointing your domain (such as pb.yourdomain.com) directly to your cloud instance IP address.

Next, open Caddy's default configuration file:

nano /etc/caddy/Caddyfile
Enter fullscreen mode Exit fullscreen mode

Replace the initial content with this complete production reverse proxy configuration:

pb.yourdomain.com {
    encode zstd gzip

    @websockets {
        header Connection *Upgrade*
        header Upgrade    websocket
    }

    reverse_proxy @websockets 127.0.0.1:8090

    reverse_proxy 127.0.0.1:8090 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
        header_up X-Forwarded-Proto {scheme}
    }

    log {
        output file /var/log/caddy/pocketbase_access.log {
            roll_size 10MB
            roll_keep 10
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Save the file. Test your syntax using Caddy's built-in validator before restarting:

caddy validate --config /etc/caddy/Caddyfile
Enter fullscreen mode Exit fullscreen mode

If the validation completes without errors, restart the Caddy service to initiate HTTPS certificate requests:

systemctl restart caddy
Enter fullscreen mode Exit fullscreen mode

Navigate your browser to https://pb.yourdomain.com/_/ to open the PocketBase administration dashboard interface. The platform will ask you to create your initial superadmin username and password.

Production Hardening, Webhooks, and Automated Backups

Running a database in production requires robust maintenance. Because PocketBase relies on SQLite, you must optimize write performance and set up consistent file backups.

Enabling Write-Ahead Logging (WAL) Mode

By default, SQLite locks the entire database during write operations. Enabling Write-Ahead Logging (WAL) mode allows concurrent read operations while writes complete in the background. PocketBase manages this setting internally, but you can verify database health and tune timeout values directly by running a initialization hook script inside /var/lib/pocketbase/pb_hooks/main.pb.js.

Create this JavaScript hook file to force performance optimizations at boot time:

onAfterBootstrap((e) => {
    $app.dao().db().newQuery("PRAGMA journal_mode=WAL;").execute();
    $app.dao().db().newQuery("PRAGMA synchronous=NORMAL;").execute();
    $app.dao().db().newQuery("PRAGMA busy_timeout=5000;").execute();
    console.log("Database pragmas configured successfully.");
});
Enter fullscreen mode Exit fullscreen mode

Integrating Dynamic Event Webhooks

When your web applications write record updates to PocketBase, you often need to forward event data out to automation engines like n8n or Temporal. When handling external HTTP requests, you should implement security mechanisms like signature validation. Check out our guide on Securing Incoming Webhooks with HMAC Signature Verification to protect target endpoints from rogue payloads.

If you are choosing between orchestration tools for processing PocketBase database mutations, read our comparison on Temporal vs n8n vs Airflow Webhook Automation to pick the best architecture for your stack.

Here is a complete custom PocketBase hook file (/var/lib/pocketbase/pb_hooks/webhooks.pb.js) that automatically sends a webhook POST request whenever a new record is added to a target collection:

onRecordAfterCreateRequest((e) => {
    const record = e.record;
    const collectionName = record.tableName();

    if (collectionName === "orders") {
        const payload = {
            id: record.get("id"),
            user: record.get("user"),
            total: record.get("total"),
            created: record.get("created")
        };

        $http.send({
            url: "https://automation.yourdomain.com/webhook/pocketbase-order",
            method: "POST",
            body: JSON.stringify(payload),
            headers: {
                "content-type": "application/json",
                "x-custom-header": "PocketBase-Event"
            },
            timeout: 10
        });
    }

    e.next();
});
Enter fullscreen mode Exit fullscreen mode

Automated Database Backups via Python and Cron

Because all SQLite tables and uploaded file assets sit within /var/lib/pocketbase/pb_data, back ups are straightforward. However, copying an active SQLite file while heavy write operations execute can corrupt your data backup. We will write a dedicated Python backup script that utilizes PocketBase built-in backup CLI features or safely snapshots the database directory.

To explore native background execution techniques outside of standard cron timers, read our complete tutorial on Scheduling Python Scripts for Automated Tasks.

Create a backup folder and script on your system:

mkdir -p /var/backups/pocketbase
nano /usr/local/bin/pocketbase_backup.py
Enter fullscreen mode Exit fullscreen mode

Paste the following python script into the file:

#!/usr/bin/env python3
import os
import shutil
import subprocess
import datetime

BACKUP_DIR = "/var/backups/pocketbase"
DATA_DIR = "/var/lib/pocketbase/pb_data"
RETENTION_DAYS = 7

def create_backup():
    now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_filename = f"pb_backup_{now}.tar.gz"
    destination_path = os.path.join(BACKUP_DIR, backup_filename)

    print(f"Starting backup process: {backup_filename}")

    temp_snapshot_dir = f"/tmp/pb_snapshot_{now}"
    os.makedirs(temp_snapshot_dir, exist_ok=True)

    try:
        sqlite_db = os.path.join(DATA_DIR, "data.db")
        snapshot_db = os.path.join(temp_snapshot_dir, "data.db")

        if os.path.exists(sqlite_db):
            subprocess.run([
                "sqlite3", sqlite_db, f".backup '{snapshot_db}'"
            ], check=True)

        shutil.copytree(
            os.path.join(DATA_DIR, "storage"),
            os.path.join(temp_snapshot_dir, "storage"),
            dirs_exist_ok=True
        )

        subprocess.run([
            "tar", "-czf", destination_path, "-C", temp_snapshot_dir, "."
        ], check=True)

        print(f"Backup created successfully at: {destination_path}")

    finally:
        if os.path.exists(temp_snapshot_dir):
            shutil.rmtree(temp_snapshot_dir)

def cleanup_old_backups():
    now = datetime.datetime.now()
    for filename in os.listdir(BACKUP_DIR):
        file_path = os.path.join(BACKUP_DIR, filename)
        if os.path.isfile(file_path) and filename.startswith("pb_backup_"):
            file_time = datetime.datetime.fromtimestamp(os.path.getmtime(file_path))
            if (now - file_time).days > RETENTION_DAYS:
                os.remove(file_path)
                print(f"Removed old backup: {filename}")

if __name__ == "__main__":
    if not os.path.exists(BACKUP_DIR):
        os.makedirs(BACKUP_DIR)
    create_backup()
    cleanup_old_backups()
Enter fullscreen mode Exit fullscreen mode

Make the Python backup script executable and test execution manually:

chmod +x /usr/local/bin/pocketbase_backup.py
apt-get install -y sqlite3
python3 /usr/local/bin/pocketbase_backup.py
Enter fullscreen mode Exit fullscreen mode

Finally, set up a system crontab entry to automatically trigger this script every night at 2:00 AM:

crontab -e
Enter fullscreen mode Exit fullscreen mode

Add this line at the end of the crontab configuration file:

0 2 * * * /usr/usr/bin/python3 /usr/local/bin/pocketbase_backup.py > /var/log/pocketbase_backup.log 2>&1
Enter fullscreen mode Exit fullscreen mode

Getting Started

Deploying PocketBase on your own server gives you full control over your database tier without paying expensive per-user cloud fees. By running it behind a production proxy like Caddy and pairing it with automated file snapshot routines, you achieve a resilient backend ready for active production traffic.

Ready to launch your instance? Secure your cloud infrastructure on Hetzner VPS or DigitalOcean, configure domain records using Namecheap, and connect your API triggers to n8n Cloud to build complex web services fast.

Outsource Your Automation

Don't have time? I build production n8n workflows, WhatsApp bots, and fully automated YouTube Shorts pipelines. Hire me on Fiverr, mention SYS3-DEVTO for priority. Or DM at chasebot.online.


Originally published on Automation Insider.

Top comments (0)