Developer Tools AWS

PocketBase 0.39.11 on AWS User Guide

| Product: PocketBase 0.39.11 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the PocketBase 0.39.11 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg backend platforms listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

PocketBase is an open source backend in a single Go binary: an embedded SQLite database, a REST API with realtime subscriptions, user authentication (password, OTP and OAuth2), file storage and an admin dashboard. Point a web or mobile app at it with the official JavaScript or Dart SDK (or any HTTP client) and you have a database, an API and authentication without running anything else.

Upstream PocketBase serves plain HTTP, and the first time it starts without an administrator it prints a one time installer link: whoever opens that link first becomes the superuser. The cloudimg image makes it safe to put on the internet:

  • HTTPS from the first boot. nginx serves PocketBase on port 443 with a certificate generated on your instance's first boot, naming the instance's DNS name and IP addresses. Port 80 only redirects to HTTPS. PocketBase itself listens on 127.0.0.1:8090 only.
  • A superuser unique to your instance, no installer link. First boot creates the superuser admin@cloudimg.local with a random password written to /root/pocketbase-credentials.txt (root only). The password is handed to PocketBase in a root only temporary file, never on a command line, and PocketBase stores only a bcrypt hash of it. PocketBase refuses to start until that superuser exists, so the first visitor installer link is never issued.
  • The real client address, with rate limiting on. nginx overwrites X-Real-IP and X-Forwarded-For with the address of the connection, and PocketBase trusts X-Real-IP only (it is reachable only through nginx). Request logs and PocketBase's built in rate limits therefore see the real client, never an address a client made up, and the rate limits are switched on (sign in attempts: 2 per 3 seconds per client).
  • Configured, not guessed, host name. The application URL PocketBase puts in e-mail links is set from the instance's EC2 DNS name at first boot (or your own name, Step 9), never from a request's Host header. Requests for host names the instance does not serve are dropped.
  • Metadata service blocked. PocketBase can send webhooks and fetch OAuth2 and remote URLs, so PocketBase, nginx and the first boot service are blocked from the instance metadata service, and the AMI requires IMDSv2.
  • Your data on its own disk. The database, uploaded files and logs (pb_data) live on a separate 16 GiB EBS volume mounted at /var/lib/pocketbase without device files, set-uid programs or execution, which you can grow and snapshot on its own.
  • The official release, verified against its published checksum, and fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: PocketBase 0.39.11 (the official release binary /usr/local/bin/pocketbase, licence in /usr/share/doc/pocketbase), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet.
  • A security group that allows TCP 22 (SSH) from your own networks only, and TCP 443 (and TCP 80, which only redirects to HTTPS) from the people and apps that use PocketBase. Nothing else needs to be open.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick PocketBase 0.39.11 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair, and a security group as above. The AMI already carries the 16 GiB data volume; make it as large as your data needs on the launch page (you can also grow it later, Step 10).

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=pocketbase}]'

Step 2: Connect to your instance

Version SSH user
PocketBase 0.39.11 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot generates the certificate and the superuser, then PocketBase and nginx start:

systemctl is-active pocketbase nginx
sudo test -f /var/lib/cloudimg/pocketbase-firstboot.done && echo FIRSTBOOT_DONE
active
active
FIRSTBOOT_DONE

Only SSH and nginx listen on the network. PocketBase listens on loopback behind nginx, and the DNS resolver listens on loopback only:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | sed -E 's/pid=[0-9]+,fd=[0-9]+/.../g' | sort
0.0.0.0:22 users:(("sshd",...),("systemd",...))
0.0.0.0:443 users:(("nginx",...),("nginx",...),("nginx",...))
0.0.0.0:80 users:(("nginx",...),("nginx",...),("nginx",...))
127.0.0.1:8090 users:(("pocketbase",...))
127.0.0.53%lo:53 users:(("systemd-resolve",...))
127.0.0.54:53 users:(("systemd-resolve",...))
[::]:22 users:(("sshd",...),("systemd",...))
[::]:443 users:(("nginx",...),("nginx",...),("nginx",...))
[::]:80 users:(("nginx",...),("nginx",...),("nginx",...))

PocketBase answers its health check over HTTPS, verified against the instance's own certificate:

curl -s --cacert /etc/nginx/tls/pocketbase.crt https://127.0.0.1/api/health; echo
{"message":"API is healthy.","code":200,"data":{}}

Step 4: Get your dashboard URL and password

The credentials file is readable by root only. This prints it with the password hidden; sudo cat /root/pocketbase-credentials.txt shows the password itself:

sudo sed -E 's/^(pocketbase.admin.pass=).*/\1<hidden>/' /root/pocketbase-credentials.txt
# PocketBase 0.39.11 - generated on first boot by pocketbase-firstboot.service
# This superuser password is unique to this instance; none was baked into the image. PocketBase keeps only a
# bcrypt hash of it. Keep this file secret.

POCKETBASE_URL=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/
POCKETBASE_ADMIN_DASHBOARD=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/_/
POCKETBASE_ADMIN_EMAIL=admin@cloudimg.local
pocketbase.admin.pass=<hidden>
POCKETBASE_CA_CERT=/etc/nginx/tls/pocketbase.crt
POCKETBASE_CERT_SHA256=E3:2A:4B:6A:1D:0B:DB:06:21:E2:75:BA:56:13:A8:70:45:67:F7:88:35:58:41:11:C9:C9:44:59:F3:76:A6:F6

# Admin dashboard:  https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/_/   (sign in as admin@cloudimg.local with the password above)
# REST API:         https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/api/
# The certificate is self-signed for this instance: add your own DNS name with
#   sudo pocketbase-set-hostname <your-dns-name>
# New superuser password (recovery):  sudo pocketbase-reset-admin-password
# User guide: https://www.cloudimg.co.uk/guides/pocketbase-0-39-11-on-ubuntu-24-04-aws/

The URL uses the instance's public DNS name from EC2 instance metadata. The certificate also names the public and private IP addresses, so https://<public-ip>/_/ works too. POCKETBASE_CERT_SHA256 is the certificate's fingerprint: compare it with the one your browser shows before you trust the certificate. To serve under your own DNS name with a certificate browsers already trust, see Step 9.

Step 5: Open the admin dashboard

Browse to the POCKETBASE_ADMIN_DASHBOARD URL. The certificate is self signed for this instance, so the browser asks you to confirm it the first time (check the fingerprint against POCKETBASE_CERT_SHA256). Sign in as admin@cloudimg.local with the password from Step 4:

The PocketBase superuser login page with the e-mail admin@cloudimg.local filled in

The Collections view lists your collections (tables). Each collection has its own fields, API rules and realtime channel; users is the built in auth collection for your app's users:

The PocketBase Collections view showing the users and notes collections and one record in notes

Click a record to edit it in place:

The PocketBase record editor for a notes record with its id and text fields

Settings holds the application name and URL, mail, file storage (local or S3 compatible), backups and scheduled jobs. The application URL was set to your instance's name at first boot, and IP proxy headers and Rate limiting are enabled:

PocketBase Settings, Application: the application URL set to the instance's EC2 DNS name, IP proxy headers Enabled and Rate limiting Enabled

Change the superuser e-mail to a real address you own (Collections > System > _superusers), so password reset e-mails can reach you once mail is configured.

Step 6: Use the REST API

Everything in the dashboard is also available over the REST API. This signs in as the superuser (the password goes to curl on standard input, so it never appears in the process list), creates a notes collection anyone can read, adds a record as the superuser and reads it back without signing in:

PB_CA=/etc/nginx/tls/pocketbase.crt
PB_TOKEN=$(sudo bash -c 'printf "{\"identity\":\"%s\",\"password\":\"%s\"}" "$(grep "^POCKETBASE_ADMIN_EMAIL=" /root/pocketbase-credentials.txt | cut -d= -f2-)" "$(grep "^pocketbase.admin.pass=" /root/pocketbase-credentials.txt | cut -d= -f2-)"' \
  | curl -s --cacert "$PB_CA" -H 'Content-Type: application/json' --data-binary @- \
    https://127.0.0.1/api/collections/_superusers/auth-with-password | jq -r '.token // empty')
pb() { curl -s --cacert "$PB_CA" -K <(printf 'header = "Authorization: %s"\n' "$PB_TOKEN") "$@"; }
[ -n "$PB_TOKEN" ] && echo "signed in as the superuser"
pb -H 'Content-Type: application/json' \
  -d '{"name":"notes","type":"base","fields":[{"name":"text","type":"text","required":true}],"listRule":"","viewRule":""}' \
  https://127.0.0.1/api/collections | jq -r '"created collection: \(.name) (\(.type))"'
pb -H 'Content-Type: application/json' -d '{"text":"Hello from PocketBase on AWS"}' \
  https://127.0.0.1/api/collections/notes/records | jq -r '"created record: \(.id)"'
curl -s --cacert "$PB_CA" 'https://127.0.0.1/api/collections/notes/records?fields=text' | jq -c '{totalItems, items}'
signed in as the superuser
created collection: notes (base)
created record: q84qc6ubowpgbx2
{"totalItems":1,"items":[{"text":"Hello from PocketBase on AWS"}]}

listRule and viewRule set to "" make the collection readable by anyone; leave them null (the default) for superuser only access, or write a rule such as @request.auth.id != "" for signed in users. Only superusers can create records here because the create rule was left unset. See API rules and filters.

From your own app, use the SDK against the instance's URL, for example with the JavaScript SDK:

import PocketBase from 'pocketbase';
const pb = new PocketBase('https://<your-domain>');
const notes = await pb.collection('notes').getFullList();

Browsers must trust the certificate first: use your own domain and a certificate from a public authority (Step 9) for apps used by other people.

Step 7: Check the security posture

A wrong password is refused, repeated sign in attempts from one client are rate limited, the settings need a superuser, a request for a host name the instance does not serve is dropped without an answer, and port 80 redirects to HTTPS on the configured name:

sleep 4
C=/etc/nginx/tls/pocketbase.crt
for i in 1 2 3; do
  printf '{"identity":"admin@cloudimg.local","password":"not-the-password-%s"}' "$i" \
    | curl -s --cacert $C -H 'Content-Type: application/json' --data-binary @- -o /dev/null \
      -w "wrong password $i   -> HTTP %{http_code}\n" https://127.0.0.1/api/collections/_superusers/auth-with-password
done
curl -s --cacert $C -o /dev/null -w 'settings, no login -> HTTP %{http_code}\n' https://127.0.0.1/api/settings
curl -s --cacert $C -H 'Host: attacker.example' -o /dev/null -w 'unknown host       -> %{http_code}\n' https://127.0.0.1/api/health || true
curl -s -o /dev/null -w 'http :80           -> %{http_code} %{redirect_url}\n' http://127.0.0.1/_/
wrong password 1   -> HTTP 400
wrong password 2   -> HTTP 400
wrong password 3   -> HTTP 429
settings, no login -> HTTP 401
unknown host       -> 000
http :80           -> 301 https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/_/

429 is PocketBase's rate limit (2 sign in attempts per 3 seconds per client address); 000 means no answer, because nginx closes the connection for a host name it does not serve.

PocketBase runs as the unprivileged pocketbase user on loopback, refuses to start without a superuser (the guard), and PocketBase, nginx and the first boot service cannot reach the instance metadata service:

ps -o user:12=,args= -C pocketbase
systemctl cat pocketbase | grep -E '^(User|ExecStartPre|ExecStart)='
systemctl show pocketbase nginx pocketbase-firstboot -p Id -p IPAddressDeny
pocketbase   /usr/local/bin/pocketbase serve --http=127.0.0.1:8090 --dir=/var/lib/pocketbase/pb_data
User=pocketbase
ExecStartPre=+/usr/local/sbin/pocketbase-guard
ExecStart=/usr/local/bin/pocketbase serve --http=127.0.0.1:8090 --dir=/var/lib/pocketbase/pb_data
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
Id=pocketbase.service

IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
Id=nginx.service

IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
Id=pocketbase-firstboot.service

The credentials file and the TLS key are root only, and the certificate was generated on this instance for its own addresses:

sudo stat -c '%a %U:%G %n' /root/pocketbase-credentials.txt /etc/nginx/tls/pocketbase.key
openssl x509 -noout -subject -ext subjectAltName -in /etc/nginx/tls/pocketbase.crt
600 root:root /root/pocketbase-credentials.txt
600 root:root /etc/nginx/tls/pocketbase.key
subject=CN = ec2-<public-ip-dashed>.compute-1.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name: 
    DNS:ec2-<public-ip-dashed>.compute-1.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost

Step 8: Superusers and passwords

Add more superusers, or change the password, from the dashboard (Collections > System > _superusers). If the password is lost, this sets a new random one for admin@cloudimg.local and records it in the credentials file (PocketBase restarts for a moment):

sudo pocketbase-reset-admin-password

For extra protection, Settings > Application > Superuser IPs limits superuser sign in to the addresses you list, and superusers can turn on multi factor authentication in the _superusers collection options.

Step 9: Your own DNS name and certificate

Point a DNS record at the instance, then set the name PocketBase is served as. This mints a new self signed certificate for the name (and the instance's IP addresses), updates nginx, PocketBase's application URL and the credentials file, and keeps the name across reboots:

sudo pocketbase-set-hostname <your-domain>

To use a certificate from a public or your own certificate authority, copy it over the generated pair and reload nginx (running pocketbase-set-hostname again replaces it with a self signed one):

sudo install -m 0644 <your-domain>.crt /etc/nginx/tls/pocketbase.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/tls/pocketbase.key
sudo nginx -t && sudo systemctl reload nginx

Step 10: The data volume and backups

The database, uploaded files and logs live on the separate EBS volume, owned by the pocketbase service account and mounted without device files, set-uid programs or execution:

findmnt -no SOURCE,FSTYPE,OPTIONS /var/lib/pocketbase
df -h --output=size,used,avail,target /var/lib/pocketbase
sudo stat -c '%a %U:%G %n' /var/lib/pocketbase /var/lib/pocketbase/pb_data
/dev/nvme1n1 ext4   rw,nosuid,nodev,noexec,relatime
 Size  Used Avail Mounted on
  16G  1.1M   15G /var/lib/pocketbase
750 pocketbase:pocketbase /var/lib/pocketbase
750 pocketbase:pocketbase /var/lib/pocketbase/pb_data

To grow it, modify the volume's size in the EC2 console, then run sudo resize2fs on its device (shown by findmnt above). Back up with EBS snapshots of this volume (from the EC2 console, aws ec2 create-snapshot, or Amazon Data Lifecycle Manager on a schedule), or with PocketBase's own Settings > Backups, which can write to S3 compatible storage on a schedule.

Step 11: Server components

Component Path
PocketBase release binary /usr/local/bin/pocketbase
Licence and version record /usr/share/doc/pocketbase/
Data: database, files, logs (EBS volume) /var/lib/pocketbase/pb_data
JavaScript hooks and migrations (optional) /var/lib/pocketbase/pb_hooks, /var/lib/pocketbase/pb_migrations
Service unit /etc/systemd/system/pocketbase.service (loopback only, guarded)
nginx site /etc/nginx/sites-available/cloudimg-pocketbase
Certificate and key /etc/nginx/tls/
First boot script /usr/local/sbin/pocketbase-firstboot.sh
Helpers /usr/local/sbin/pocketbase-set-hostname, pocketbase-reset-admin-password
Credentials /root/pocketbase-credentials.txt (mode 0600)
pocketbase --version
cat /usr/share/doc/pocketbase/VERSION
nginx -v 2>&1
pocketbase version 0.39.11
PocketBase 0.39.11 (official release, MIT, sha256 08b9fcda0d5fd42cb315dc15a36dfa121c993855bd635f01d347c31b4328ec34)
nginx version: nginx/1.24.0 (Ubuntu)

Logs: sudo journalctl -u pocketbase for the service, Logs in the dashboard for requests (with the real client address), /var/log/nginx/access.log for nginx.

To move to a newer PocketBase release, download the linux_amd64 zip from the PocketBase releases page, check it against the release's checksums.txt, snapshot the data volume, replace /usr/local/bin/pocketbase (owned by root, mode 0755) and run sudo systemctl restart pocketbase. The built in pocketbase update command is not used on this image because the binary is owned by root. Your data in /var/lib/pocketbase/pb_data is kept and migrated by PocketBase on start.

Step 12: Security recommendations

  • Allow port 22 in the security group from your own networks only.
  • Change the superuser e-mail to a real address, configure mail (Settings > Mail settings) and consider Superuser IPs and multi factor authentication for superusers (Step 8).
  • Review the API rules of every collection before you store real data: a rule of "" means anyone.
  • Use your own DNS name and a trusted certificate (Step 9), and snapshot the data volume or enable PocketBase backups (Step 10).
  • Keep the operating system patched; unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else. Watch the PocketBase releases for security fixes.

Support and licensing

PocketBase is open source under the MIT licence; there is no per user or per CPU licence fee. The licence is in /usr/share/doc/pocketbase/. cloudimg is not affiliated with or endorsed by the PocketBase project. The AMI price covers cloudimg's hardening, first boot setup and support.

  • Email: support@cloudimg.co.uk
  • Website: https://www.cloudimg.co.uk
  • Support: 24/7, with a 24 hour response SLA