How to Run PocketBase in a Sandbox With a Private HTTPS URL
Updated October 27, 2026 .md
Short answer: Download the PocketBase binary into a small Sandboxer sandbox, create a superuser with its CLI and run it as a systemd service on port 8090. sbx host gives the dashboard and API a private HTTPS URL. Clients send the __sbx_auth cookie plus PocketBase's own token. Records and a live realtime stream survived stop and resume.
PocketBase is a backend in one Go binary: SQLite, auth, file storage, a REST API, realtime subscriptions and an admin dashboard. No Docker and no separate database, so it fits in a small sandbox with room to spare.
What we ran#
Everything below ran on production on October 27, 2026, in one small sandbox (2 vCPU, 4 GB RAM, 12 GB disk, $0.018/hour, region eu-central) with PocketBase 0.40.6. Tokens are replaced with <token>. Times are single runs unless the table says otherwise.
| Step | Result |
|---|---|
sbx new --type small |
3.2 s to ready |
| Binary size | 32 MB, one file |
systemctl enable --now to a passing /api/health |
165 ms |
| Memory after start | 28 MB resident |
| List records through the hosted URL, from our test machine | median 0.19 s over 20 calls (0.19 to 0.20 s) |
Same call inside the sandbox on localhost:8090 |
median 0.3 ms over 20 calls |
| Realtime event through the hosted URL | arrived 197 ms after the create request was sent |
sbx stop --wait, then sbx resume |
2.2 s and 1.3 s, memoryRestored: true, same PID |
| Kill the process, systemd restarts it | back after 5.1 s (RestartSec=5s) |
1. Install the binary and a superuser#
PocketBase ships prebuilt zips on its releases page. Unpack it into its own folder; PocketBase keeps its data in pb_data next to the binary.
sbx new --name pocketbase --type small --no-env
id=sb_v6tllk1ul8 # the id that sbx new printed; its subdomain was early-stormy-puffin
sbx exec $id --timeout 600 "mkdir -p ~/pb && cd ~/pb \
&& curl -fsSLo pb.zip https://github.com/pocketbase/pocketbase/releases/download/v0.40.6/pocketbase_0.40.6_linux_amd64.zip \
&& python3 -m zipfile -e pb.zip . && chmod +x pocketbase && rm pb.zip && ./pocketbase --version"pocketbase version 0.40.6unzip is also installed in the sandbox if you prefer it. sbx exec stops a command after 30 seconds by default, so the download gets --timeout 600.
Create the first superuser from the command line, before the server ever starts. Then nobody can claim the dashboard by opening it first:
sbx exec $id "cd ~/pb && ./pocketbase superuser upsert admin@example.com '<password>'"Successfully saved superuser "admin@example.com"!2. Run it as a systemd service#
This is the unit from PocketBase's going to production page, changed to run as the sandbox's user account and to listen on 0.0.0.0:8090 instead of serving a domain itself. TLS comes from the hosted URL, so PocketBase doesn't need its own certificate.
# pocketbase.service
[Unit]
Description = pocketbase
[Service]
Type = simple
User = user
Group = user
LimitNOFILE = 4096
Restart = always
RestartSec = 5s
StandardOutput = append:/home/user/pb/std.log
StandardError = append:/home/user/pb/std.log
WorkingDirectory = /home/user/pb
ExecStart = /home/user/pb/pocketbase serve --http=0.0.0.0:8090
[Install]
WantedBy = multi-user.targetsbx scp pocketbase.service $id:/home/user/pb/
sbx exec $id "sudo cp ~/pb/pocketbase.service /etc/systemd/system/ && sudo systemctl daemon-reload && sudo systemctl enable --now pocketbase \
&& until curl -fs localhost:8090/api/health >/dev/null; do sleep 0.05; done; curl -s localhost:8090/api/health; ps -o pid,rss,cmd -C pocketbase"Created symlink /etc/systemd/system/multi-user.target.wants/pocketbase.service → /etc/systemd/system/pocketbase.service.
{"message":"API is healthy.","code":200,"data":{}}
PID RSS CMD
657 27956 /home/user/pb/pocketbase serve --http=0.0.0.0:8090From enable --now to a healthy API took 165 ms. enabled means PocketBase also starts on a boot from disk, and Restart=always brings it back if it dies. We killed it with sudo systemctl kill -s KILL pocketbase, and it answered again 5.1 seconds later, after the 5-second RestartSec.
3. Host the dashboard and API on one private URL#
sbx host $id 8090 --title PocketBase{"ok":true,"type":"port.hosted","sandboxId":"sb_v6tllk1ul8","port":8090,"url":"https://early-stormy-puffin-8090.box.sandboxer.com?_token=<token>","access":"private",…}The dashboard is at /_/ and the API at /api/ on the same host:
| Request | Result |
|---|---|
/_/ or /api/health, no token |
401 from the Sandboxer proxy; PocketBase never sees it |
/_/?_token=<token> |
302 to /_/, sets the __sbx_auth cookie |
/_/ with the cookie |
200, the dashboard; sign in as the superuser |
/api/health with the cookie |
200, {"message":"API is healthy."…} |
In a browser, open the full ?_token= link once and the cookie does the rest.
4. Call the API from outside the sandbox#
There are two gates. The proxy wants the __sbx_auth cookie (its value is the URL's token). PocketBase wants its own auth token in the Authorization header for anything its API rules don't open up. The proxy passes Authorization through untouched, so both work together. The Gitea guide explains how to tell the proxy's 401 from the app's.
Sign in as the superuser through the hosted URL (auth API):
B=https://early-stormy-puffin-8090.box.sandboxer.com
curl -s -o /dev/null -c jar "$B/?_token=<token>" # trade the token for the cookie once
curl -s -b jar -H 'Content-Type: application/json' \
-d '{"identity":"admin@example.com","password":"<password>"}' \
$B/api/collections/_superusers/auth-with-password > auth.json
PB=$(python3 -c "import json; print(json.load(open('auth.json'))['token'])")Create a collection with the collections API. The empty-string rules let anyone who gets past the proxy list, view and create notes. null keeps update and delete for superusers only:
{"name":"notes","type":"base","fields":[{"name":"text","type":"text","required":true}],
"listRule":"","viewRule":"","createRule":"","updateRule":null,"deleteRule":null}curl -s -b jar -H "Authorization: $PB" -H 'Content-Type: application/json' -d @notes.json $B/api/collections
curl -s -b jar -H 'Content-Type: application/json' -d '{"text":"hello from my laptop"}' $B/api/collections/notes/records
curl -s -b jar "$B/api/collections/notes/records?fields=id,text"{"id":"pbc_3395098727","listRule":"","viewRule":"","createRule":"",…,"name":"notes","type":"base",…}
{"collectionId":"pbc_3395098727","collectionName":"notes","id":"doqgofal7a1xky6","text":"hello from my laptop"}
{"items":[{"id":"doqgofal7a1xky6","text":"hello from my laptop"}],"page":1,"perPage":30,"totalItems":1,"totalPages":1}Creating a collection with the cookie but without Authorization got PocketBase's own answer:
{"data":{},"message":"The request requires valid record authorization token.","status":401}Over 20 list calls from our test machine, the median was 0.19 s. Inside the sandbox the same call took 0.3 ms, so the time is the network path to eu-central, not PocketBase. Code that runs in the sandbox should call http://localhost:8090 directly.
5. Realtime works through the proxy#
PocketBase pushes record changes over server-sent events: open /api/realtime, read the PB_CONNECT event for a client id, then post the topics you want. We did this with curl from our test machine, through the private URL:
curl -sN -b jar $B/api/realtime > sse.log & # keep the stream open
cid=$(grep -m1 '^id:' sse.log | cut -d: -f2)
curl -s -b jar -H 'Content-Type: application/json' \
-d "{\"clientId\":\"$cid\",\"subscriptions\":[\"notes\"]}" $B/api/realtime # 204
curl -s -b jar -H 'Content-Type: application/json' -d '{"text":"pushed over SSE"}' \
$B/api/collections/notes/recordsevent:PB_CONNECT
data:{"clientId":"<client id>"}
event:notes
data:{"record":{"collectionId":"pbc_3395098727","collectionName":"notes","id":"h6milz97ue5mem5","text":"pushed over SSE"},"action":"create"}The event reached the open stream 197 ms after we sent the create request, about the same as one API round trip. The proxy doesn't buffer the stream.
6. Stop and resume: the stream stayed open#
We left that curl stream running and stopped the sandbox:
sbx stop $id --wait # 2.2 s
sbx resume $id # 1.3 s
sbx info $id # "memoryRestored": true
sbx exec $id "pgrep -x pocketbase; systemctl show -p NRestarts pocketbase"657
NRestarts=0Same PID, no restarts: memory was restored, so PocketBase never noticed. Both notes were still there, the hosted URL and cookie still worked, and the curl stream we opened before the stop was still connected. A note created after the resume arrived on it. That was one test, and the pause was short. Write clients to reconnect and resubscribe anyway.
A resume boots from disk instead when you resize or no machine with the same CPU type is free. Then processes are gone but files stay, and the enabled unit starts PocketBase again on the same pb_data. Expect open realtime streams to drop in that case; we didn't force a boot from disk.
7. Take a backup out before you delete#
PocketBase can zip its own data with the backups API (superuser only). The zip lands in pb_data/backups, and sbx scp copies it out:
curl -s -b jar -H "Authorization: $PB" -H 'Content-Type: application/json' \
-d '{"name":"before-delete.zip"}' $B/api/backups # 204
sbx scp $id:/home/user/pb/pb_data/backups/before-delete.zip .
python3 -m zipfile -l before-delete.zipFile Name Modified Size
data.db 2026-10-27 10:06:42 167936
auxiliary.db 2026-10-27 10:06:42 45056
types.d.ts 2026-10-27 10:05:46 802641Then delete the sandbox. The hosted URL returned 404 right after:
sbx delete $id --yesA small sandbox costs $0.018/hour while it runs and nothing while it is stopped.
FAQ#
Do I need Docker for PocketBase?#
No. It is one 32 MB binary with SQLite built in. A systemd unit gives you restart on crash and start on boot.
Is small big enough?#
For development and demos, yes. PocketBase used 28 MB of RAM after start, and a small sandbox has 4 GB. Moving to a smaller size isn't supported yet, so starting small is the safe choice. You can resize up when you need to.
Should I make the URL public?#
Only if your API rules are ready for strangers. sbx host $id 8090 --public drops the proxy's check, so the dashboard login page and every collection rule set to "" are open to anyone with the URL. We kept ours private. Switching from public back to private mints a new token, so every client needs the new cookie.
Can my frontend use the hosted URL?#
Yes, if it sends the __sbx_auth cookie. A browser that opened the ?_token= link once has it. For a server or script, send Cookie: __sbx_auth=<token> on every request, plus PocketBase's own Authorization header where your rules need it. We tested this with curl.
Where is my data?#
In ~/pb/pb_data on the sandbox disk: data.db holds your collections and records. It survives stop, resume and auto-stop. It doesn't survive sbx delete, so take a backup first.
Related#
More guides
For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.
For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.
The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.
If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.