pocketbase

Activity, over time

Daily observations from the public event archive.

Attention

Daily stars & forks

Loading attention chart

Star accumulation

Cumulative stars

Loading star accumulation chart

Pull requests

Daily opens & merges

Loading pull requests chart

Conversations

Daily issues & comments

Loading conversations chart

Community

Daily accounts & reviews

Loading community chart

Releases

Daily releases

Loading releases chart

Mentions

9posts · 30d
Nov 30 posts
  1. PB, GitHubも捨てて自作するんですね "It is practically a side-project of a side-project of a side-project" "This is not a commercial project. The project's popularity has no value for me" ちょうかっこいい。ゼロLLMでフルオーガニックなプロジェクト pocketbase#7841

    35 views
  2. Your MVP may not need six backend services and a DevOps team. pocketbase (~62k★): Single-binary backend with auth, database, and files. pocketbase/pocketbase Would a single-binary backend fit your next MVP?

    5 views
  3. PocketBase: The Open-Source Firebase Alternative That Runs in a Single Go Binary PocketBase: The Open-Source Firebase Alternative That Runs in a Single Go Binary You've built a side project. It needs a backend. You don't want to set up PostgreSQL, configure Redis, write auth from scratch, wrestle with Docker Compose, or pay Firebase prices when your user count grows. What if the entire backend - database, realtime subscriptions, authentication, file storage, and an admin dashboard - was a single 15 MB Go binary? That is PocketBase. --- What PocketBase Actually Is PocketBase is an open-source backend platform that packages everything into one statically linked executable: - Embedded SQLite database with WAL mode and dual connection pools - Realtime subscriptions via Server-Sent Events (SSE) - Built-in authentication (email/password, OAuth2, MFA, passwordless) - File storage with local or S3 backends - Admin dashboard UI served from the same binary - REST-ish API for all of the above - Go framework - embed it in your own code for custom business logic No separate database server. No Redis. No message queue. Just ./pocketbase serve and you're running. --- The Architecture: How It Fits in One Binary SQLite as the Foundation PocketBase uses modernc.org/sqlite - a pure Go port of SQLite. No CGO required. This means: - Cross-compilation works out of the box (GOOS=linux GOARCH=amd64 go build) - Zero system dependencies at runtime - The database file is just a file - copy it, back it up, move it anywhere Dual Database Design PocketBase maintains two separate SQLite databases: | Database | File | Purpose | |----------|------|---------| | Main | data.db | Collections, records, settings, auth data | | Auxiliary | auxiliary.db | Request logs, system metadata | Why two? Log writes never block critical record operations. Backups are simpler - back up data.db without the noise. Both run in WAL mode with optimized pragmas. Connection Pool Architecture: Solving SQLITE_BUSY SQLite allows one writer at a time. PocketBase solves this with separate pools per database. The nonconcurrent pool serializes all writes through a single connection. No lock contention. No SQLITE_BUSY errors. Reads scale across many connections. Smart Query Routing: dualDBBuilder PocketBase automatically routes queries based on SQL type. During transactions, both pools point to the same *dbx.TX - routing is bypassed, atomicity guaranteed. --- Realtime Subscriptions: SSE, Not WebSockets PocketBase uses Server-Sent Events (SSE) - plain HTTP, no WebSocket upgrade handshake. Why SSE? - Works through every proxy, load balancer, and corporate firewall - Automatic reconnection built into browsers (EventSource API) - Simpler server implementation - just a long-lived GET /api/realtime - Unified API: writes go through regular REST endpoints, reads through SSE How It Works 1. Client connects -> GET /api/realtime -> receives PB_CONNECT with clientId 2. Client subscribes -> POST /api/realtime with subscriptions 3. Server broadcasts on record create/update/delete -> checks access rules -> sends SSE event Chunked iteration (150 clients per goroutine) + errgroup = horizontal scaling without blocking the event loop. --- Authentication: Batteries Included PocketBase handles the auth flows most teams rebuild poorly: | Feature | Implementation | |---------|----------------| | Email/password | Bcrypt, email verification, password reset | | OAuth2 providers | Google, GitHub, Apple, Discord, etc. - 20+ built-in | | MFA (TOTP) | RFC 6238, QR code generation, backup codes | | Passwordless | Magic links, OTP via email/SMS | | Token management | JWT access tokens + refresh tokens, configurable expiry | | Auth collections | Any collection can be an auth collection | Auth records are just records in a collection with type: "auth". The same API rules control access. --- File Storage: Local or S3 Files attach to records via file field type. Storage backend is swappable. Uploads are streaming - large files don't load into memory. Served via signed URLs or public access with token-based auth. --- The Admin Dashboard: Not an Afterthought Visit http://localhost:8090/_/ - you get: - Collection schema editor (drag-and-drop field types) - Record browser with filters, sorting, inline edit - User management (auth collections) - Settings UI (mail, S3, rate limits, CORS, logs) - API playground with auto-generated request snippets - Real-time log viewer Built with Vue 3 + TypeScript, embedded as static assets in the binary. --- Extending PocketBase: Go Framework Mode Don't like something? Hook into it. PocketBase exposes custom routes, event hooks, and cron jobs. Build your custom binary: go build -o myapp -> single executable with your logic baked in. --- When PocketBase Shines (And When It Doesn't) Great For - Side projects & MVPs - zero infra, deploy to a $5 VPS - Mobile app backends - realtime sync, offline-first friendly - Internal tools - admin UI included, auth done - Prototypes - schema changes via UI, instant feedback - Low-to-moderate write throughput - SQLite handles thousands of writes/sec on SSD Not Ideal For - High-concurrency write-heavy workloads - SQLite serializes writers - Horizontal scaling - single-node architecture - Complex relational queries - no JOINs across collections - Multi-region active-active - not designed for geo-distribution --- Quick Start: 30 Seconds to Running Backend # Download prebuilt binary (Linux/macOS/Windows) curl -L https://github.com/pocketbase/pocketbase/releases/download/v0.23.0/pocketbase_0.23.0_linux_amd64.zip -o pb.zip unzip pb.zip ./pocketbase serve --http=0.0.0.0:8090 Open http://localhost:8090/_/ -> create admin -> create collections -> done. Or embed in Go with custom routes and hooks. --- The Mental Model Shift Most developers think "backend = separate services." PocketBase proves that for a huge class of applications, a single binary with SQLite is not a toy - it's the right architecture. You get: - Zero operational burden - no services to monitor, no config drift - Portability - copy the binary + data dir, run anywhere - Speed - no network hops between app and DB - Simplicity - one codebase, one deploy artifact, one thing to understand The trade-off is deliberate: you accept single-node limits in exchange for radical simplicity. For most projects, that's the correct trade-off. --- Resources - GitHub: pocketbase/pocketbase - 39k+ stars - Docs: pocketbase.io - Demo: demo.pocketbase.io - Discord: Active community for questions and showcase --- Stop over-engineering your backend. Download PocketBase, run it, build your product.

  4. DEV · · @facilishz

    PocketBase: 66 fingerprints and 17 hosts on its default port describe a young product, not a small risk PocketBase: 66 fingerprints and 17 hosts on its default port describe a young product, not a small risk Small numbers invite the wrong conclusion. A query that returns 66 matches for an application fingerprint looks like a product nobody uses, and a query that returns 17 matches for that fingerprint on a specific port looks like a rounding error. Both readings miss what the product is. The three measurements On 23 October 2026, ZoomEye returned 66 matches for app="PocketBase", 119 matches for title="PocketBase", and 17 matches for title="PocketBase" && port="8090". The fingerprint and title counts are close enough to suggest a working signature and a modest public population. The port-constrained count is the interesting one: only 17 of the 66 fingerprinted hosts present the product's documented default port. PocketBase's own documentation shows the default listener on 8090. What the product is PocketBase is a single-binary backend. It bundles an embedded database, an authentication layer, file storage, a realtime subscription API, and an administrative dashboard in one executable. The design goal is that a developer can run one file and have a complete backend, including an admin UI, with no external dependencies. That packaging is the reason the counts are small and the reason they should not be dismissed. A single binary is easy to drop onto a VPS for a prototype, a side project, or an internal tool. It is also easy to leave running after the prototype becomes something people depend on, because there is no package manager update, no container orchestration review, and no infrastructure team in the loop. The administrative surface PocketBase's dashboard is served by the same process as the application API. The dashboard is where collections, rules, and admin accounts are defined. Access rules in PocketBase are configured per collection, and the default is not always restrictive: a collection created during development frequently ends up with public read or write rules that were convenient at the time. The consequence is that a public PocketBase instance is not simply a backend with a login form. It is a backend where the authorisation model is expressed as rules that a developer wrote, and those rules are the actual access control. A single permissive rule on a collection exposes data with no authentication step at all. The version matters for a different reason. PocketBase has changed its API and its security model across releases, and a binary from an early release behaves differently from a current one. The counts here say nothing about which release a host runs. Reading 17 out of 66 The port observation is a proxy for how the deployment was done. A host on 8090 is a host configured with the documented default, which usually means a developer followed the docs and did not add a reverse proxy. A host on 443 with a hostname has had at least one production conversation. Neither configuration is safe or unsafe in itself. The distinction is useful because 17 is a number small enough that a defender can think about the whole category rather than a sample. If your organisation has a PocketBase instance, it is one of a small global population, and the reason it exists is almost certainly a specific project rather than a platform standard. What to do about a PocketBase deployment Enumerate the collections and their rules. That is the authorisation model, and reviewing it is more useful than checking whether the dashboard requires a password. Check whether the dashboard is reachable from the same network as the application. Separating them is a reverse-proxy rule in most deployments, and it removes an administrative surface from the internet. Confirm the release and read the changelog for the interval between your version and current. PocketBase's release notes document security-relevant changes, and the interval is the part that matters rather than the specific version string. Then check backups. A single-binary deployment makes backup easy to forget, because there is no database server to snapshot. A compromised or lost instance with no backup is a total loss of the data the collections held. The measurement point Sixty-six and seventeen are small numbers in a dataset where other products in this batch return tens of thousands. Small does not mean unimportant. It means the population is knowable, and a knowable population is one where an operator can move from an external count to an internal decision in a single step. References - PocketBase documentation, pocketbase.io - PocketBase project repository, pocketbase/pocketbase - ZoomEye AI query results collected 23 October 2026 for app="PocketBase", title="PocketBase", and title="PocketBase" && port="8090", sub-type all

  5. X · · @YoDevp

    Repo y docs de PocketBase. También se usa como librería de Go: le sumas tu lógica y compilas un solo binario estático. Y dato curioso: hoy solo los colaboradores pueden abrir PRs, por el spam de PRs hechos con LLMs. pocketbase/pocketbase pocketbase.io

    1 like · 8 views
  6. 做个小应用,后台还得从零写?🧱 PocketBase 用一个可执行程序提供 SQLite、用户认证、文件管理和 API。 快速做原型很合适。🚀 pocketbase/pocketbase #Backend #SQLite #浇朋友 #蓝朋友 #互关 #fo

    2 likes · 1 repost · 63 views
  7. DEV · · @adelbeit

    Deploy your own database as a remote backend service in less than an hour <center>Context</center> Every once in a while I have to stand up a new backend/DB. I used to just use Supabase, but I don't like the idea of not owning my data, and I like my bill being the same number every month. I personally love Pocketbase for this. It's ✨one binary✨ with a database (SQLite), auth, file storage, a REST API and an admin dashboard baked in. No install wizard, no 14 microservices, no YAML cathedral. It's what I reach for whenever a project needs a small, self-contained backend. If you want to compare other options, here are some other alternatives you could check out. <center>Challenge</center> I've now set this up for a handful of projects, and every single time I had to rediscover the steps like a goldfish with a keyboard. So this is the cheat sheet. What we're building By the end, requests will flow like this: client (any app that calls your backend) → Reverse proxy tool (nginx) (HTTPS, on a server) → Docker container (PocketBase) → volume (your database) !Diagram: a client sends HTTPS requests to nginx on the VPS, which forwards them to PocketBase inside a Docker container, with the data stored in a volume. Direct access to the container's port is blocked. The game plan (Agile style) 1. Run PocketBase in a local dev environment 2. Put it behind nginx 3. Containerize it and keep it behind the local nginx 4. Put nginx and the container on a VPS (remote env), with a domain and HTTPS 5. Introduce a container registry for versioning our image (in our case GHCR) 6. Automate the deploys with GitHub Actions or another CI/CD tool What you'll need Here's the stack I'm using, and what each piece is called in case you want to swap in your own: - A backend/database: I'll be using PocketBase, an open source backend in a single executable with SQLite built in - A container platform: I'm using Docker (and Docker Compose) because it's what I like to use the most - A reverse proxy: it routes incoming requests to the right app, handles HTTPS, and enforces limits. I'll be using nginx - Version control: Git, hosted on GitHub, because I like GitHub - A container registry: a place to store our built Docker images. I use GHCR (GitHub Container Registry) - A CI/CD tool: continuous integration/continuous delivery, the thing that builds and deploys for you on every push. I use GitHub Actions - A remote environment: a VPS (virtual private server) to run it all on. I love DigitalOcean droplets - A domain/subdomain for making the DB accessible on the internet - Optional A health check monitor: a service that keeps pinging PocketBase's health endpoint and alerts you (or triggers a rollback) when it stops responding and last but not least, a handy dandy terminal with basic knowledge of SSH. > BONUS: some music to help you lock in and time yourself. If you don't finish this tutorial within an hour, I'll personally refund you. Everything in angle brackets like <username> or <website-url> is a placeholder for your own value. 1. PocketBase, locally Nothing fancy yet. We want PocketBase running on your machine so you know what we're about to ship. Here's how: # grab the binary for your OS/arch from https://github.com/pocketbase/pocketbase/releases # (put it in a `pocketbase/` folder in your project) cd pocketbase # run the migrations, then start the server PB_ENV=dev ./pocketbase migrate up PB_ENV=dev ./pocketbase serve # create yourself a superuser ./pocketbase superuser upsert dev-admin@example.com change-me-locally If you're on Windows that's pocketbase.exe. The admin dashboard lives at http://127.0.0.1:8090/_/. You don't actually need PB_ENV (it isn't a PocketBase thing, just an environment variable I made up) or cross-env (a tiny tool that makes PB_ENV=dev work in any shell, Windows included), but they're useful for one very specific thing. Locally I want a user to log in with so I'm not creating one by hand every time I wipe my database. But I definitely don't want that user existing in production. So let's make a migration that only runs when PB_ENV=dev. ./pocketbase migrate create dev_user // pb_migrations/<timestamp>_dev_user.js /// <reference path="../pb_data/types.d.ts" /> migrate((app) => { // bail out unless we are in dev if ($os.getenv("PB_ENV") !== "dev") return; let existing; const DEV_EMAIL = "dev@example.com"; const DEV_PASS = "change-me-locally"; try { existing = app.findFirstRecordByData("users", "email", DEV_EMAIL); } catch (e) { existing = null; } if (existing) return; const record = new Record(app.findCollectionByNameOrId("users")); record.set("email", DEV_EMAIL); record.set("password", DEV_PASS); record.set("verified", true); app.save(record); }, (app) => { // same guard on the way down if ($os.getenv("PB_ENV") !== "dev") return; let existing; try { existing = app.findFirstRecordByData("users", "email", "dev@example.com"); } catch (e) { existing = null; } if (existing) app.delete(existing); }) That PB_ENV check is <mark>the whole trick</mark>. In production the migration runs, shrugs, and does nothing. Here's what we've achieved so far: - ✅ PocketBase running locally - ✅ a superuser and a dev-only user - ✅ migrations that know which environment they're in 2. Put it behind nginx Right now you're talking to PocketBase directly on port 8090. That's fine on your laptop, but it's not what you want facing the internet. Before we touch the server, let's get used to the setup we'll use there. What is nginx doing here? It's a <mark>reverse proxy</mark>. It sits in front of PocketBase, accepts the incoming requests, and forwards them to it. That gives us a few things: - HTTPS. nginx handles the TLS certificate so PocketBase doesn't have to - One front door for many apps. Several apps can share one server and one port 443, routed by domain name - Guard rails. Rate limits and a max upload size, before a request ever reaches PocketBase - Privacy. The app itself only listens on localhost and can't be reached directly from the internet What else could you use? - Caddy is simpler, and gets and renews HTTPS certificates automatically - Traefik is built for containers and configures itself from Docker labels - PocketBase can also serve HTTPS by itself if you hand it a domain, which is the least moving parts if it's the only thing on the box Why nginx then? For me it's familiarity and it gives me everything I need at the moment out of the box; Rate limiting and upload limits in one config file. If you're starting from scratch with a single app, Caddy is a perfectly good answer as well. Install nginx (brew install nginx, sudo apt install nginx, or the zip from nginx.org on Windows) and add this server block to your config. Where it goes depends on your OS: conf.d/, sites-enabled/, or inside http {} in nginx.conf. # local dev: nginx listens on 8080 and forwards to PocketBase on 8090 server { listen 8080; server_name localhost; # max upload size, i.e. maxSelect:20 * maxSize:10MB per request client_max_body_size 250m; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; location / { proxy_pass http://127.0.0.1:8090; } } nginx -t # check the config nginx -s reload # (or start nginx if it isn't running yet) Now http://localhost:8080/_/ shows the same dashboard, but through nginx. If you plan to use PocketBase's realtime subscriptions, double check PocketBase's docs for the extra nginx proxy settings they recommend. Later we'll add a rate limit for any collection that strangers are allowed to write to. For now, one thing to take away: nginx doesn't care what's behind it, as long as something answers on the port you give it. Which brings us to the next step. 3. Put PocketBase in Docker Compose, behind the same nginx Now we put PocketBase in a box, a container, 🐳 What does Docker give us? It's a container platform: it packages an app and everything it needs into an image, and runs that image as an isolated container. That makes it easy to set up the same environment across platforms/machines with a single command. To update you just pull the latest image, rollback is the same with an older image, and your data stays safe in a volume. What else could you use? - Run the binary directly under systemd. Fewer layers, totally valid for a hobby project, but you're updating and wiring it up by hand every time - Podman, a Docker-compatible alternative that runs without a background daemon - Kubernetes (or a lighter orchestrator like Docker Swarm or Nomad). They run and scale containers across many servers, which is way more than one small PocketBase on one box needs, but good to know they exist for when you outgrow this - A managed PocketBase host, or a PaaS with persistent disks, if you'd rather not run a server at all Here's the Dockerfile. Notice we only copy in pb_hooks and pb_migrations, the code, not the data. Your <mark>actual database lives in a volume</mark>. # pocketbase/Dockerfile FROM alpine:3.20 # pin whatever version you tested against, bump it on purpose ARG PB_VERSION=0.40.2 RUN apk add --no-cache unzip ca-certificates curl WORKDIR /pb RUN curl -L -o pb.zip https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_amd64.zip \ && unzip pb.zip -d /pb/ \ && rm pb.zip \ && chmod +x /pb/pocketbase COPY pb_hooks /pb/pb_hooks COPY pb_migrations /pb/pb_migrations EXPOSE 8090 CMD ["/pb/pocketbase", "serve", "--http=0.0.0.0:8090"] Heads up, those two COPY lines will fail the build if the folders don't exist. That's why running migrate up locally first matters, it makes sure pb_migrations/ is there so the image can actually be built. And the compose file: # pocketbase/docker-compose.yml services: pocketbase: build: . restart: unless-stopped ports: - "127.0.0.1:8091:8090" environment: - PB_ENV=${PB_ENV:-production} volumes: - pb_data:/pb/pb_data volumes: pb_data: A few small details doing big work here: - 127.0.0.1:8091:8090 reads as host-ip:host-port:container-port, and the two port numbers are different on purpose so you can see which is which: - 8090 is the port PocketBase listens on inside the container. It's fixed by the Dockerfile and nobody outside the container can see it - 8091 is the port Docker listens on on the machine itself (the host). It passes whatever arrives there into the container's 8090. This is the port nginx forwards to, that's the proxy_pass. The public internet never calls 8091, it talks to nginx - 8090 is the default and you could have both of them ast 8090 or use any free port. - That 127.0.0.1 (instead of 0.0.0.0) means the container is only reachable through nginx and <mark>not directly from the internet</mark> - PB_ENV=${PB_ENV:-production} defaults to production, so you have to ask for dev on purpose. Our dev-only migration stays asleep unless you say otherwise - the pb_data volume, so recreating the container doesn't delete your database You can stop the binary from step 1 now (Ctrl+C). Start the container in dev mode, then point nginx at the new port by changing proxy_pass to http://127.0.0.1:8091 and reloading: cd pocketbase PB_ENV=dev docker compose up -d --build # PowerShell: $env:PB_ENV="dev"; docker compose up -d --build nginx -s reload http://localhost:8080/_/ works again, same nginx, different thing behind it. The container starts with an empty database, so run the superuser command again: docker compose exec pocketbase /pb/pocketbase superuser upsert dev-admin@example.com change-me-locally Here's what we've achieved so far: - ✅ PocketBase running locally, directly and in a container - ✅ nginx in front of it 4. Put it on a VPS Same two pieces, nginx and the compose file, but this time on a server on the internet. Why a VPS? I get a real Linux box with full control, a flat monthly price, and a place for several small apps. PocketBase wants a persistent disk, and a VPS has one. Any provider works. A few to look at: - DigitalOcean (Droplets) - AWS (Lightsail or EC2) - Microsoft Azure (Virtual Machines) - Google Cloud (Compute Engine) - Hetzner (Cloud servers) Here's the order, and the order matters: DNS first Add an A record pointing your subdomain (api.yourdomain.com, or anything you like) at the server's IP. DNS takes a while to propagate and we need it ready for HTTPS, so do it now and do the rest while it settles. Prep the server Log in as root and install what we need (Debian/Ubuntu shown, Docker via the install guide): apt update && apt install -y nginx certbot python3-certbot-nginx Then create a dedicated user for deploys, <mark>don't deploy as root</mark>: sudo adduser <username> sudo mkdir -p /opt/<dirname> sudo chown -R <username>:<username> /opt/<dirname> # the user needs docker access sudo usermod -aG docker <username> # somewhere for the deploy key to live sudo -u <username> mkdir -p -m 700 /home/<username>/.ssh And not on the server, on your own machine, make a deploy key and give the server the public half: # make a dedicated deploy key ssh-keygen -t ed25519 -C <username> -f ~/.ssh/<keyname> -N "<passphrase>" # log in as root once and copy the public key over ssh root@<vps-ip> "cat >> /home/<username>/.ssh/authorized_keys" < ~/.ssh/<keyname>.pub # or, if you want to prepend restrictions to the key sed 's/^/no-port-forwarding,no-agent-forwarding /' ~/.ssh/<keyname>.pub | ssh root@<vps-ip> "cat >> /home/<username>/.ssh/authorized_keys" # make sure the new user can actually log in ssh <username>@<vps-ip> -i ~/.ssh/<keyname> A dedicated deploy key is cleaner than reusing your personal one, if it leaks you revoke that one key and nothing else. Get the files there and start it For now we'll build the image right on the server. Run this from your pocketbase/ folder (we're sending the code, not your local data): scp -r Dockerfile docker-compose.yml pb_hooks pb_migrations <username>@<vps-ip>:/opt/<dirname>/ ssh <username>@<vps-ip> -i ~/.ssh/<keyname> "cd /opt/<dirname> && docker compose up -d --build" No PB_ENV this time, so it defaults to production and the dev user is never created. The server's nginx Same idea as before, with three upgrades: your real domain, the production port, and a rate limit. # throttle public write endpoints to curb spam limit_req_zone $binary_remote_addr zone=<zone-name>:10m rate=5r/m; server { listen 80; server_name <website-url>; # max upload size, i.e. maxSelect:20 * maxSize:10MB per request client_max_body_size 250m; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # any collection with a public (unauthenticated) createRule location = /api/collections/<collection>/records { limit_req zone=<zone-name> burst=3 nodelay; proxy_pass http://127.0.0.1:8091; } location / { proxy_pass http://127.0.0.1:8091; } } That limit_req block is for any collection where you let strangers create records, like a contact form. If the rule is public, the internet will find it. Rate limit it. Copy it over, enable it, and issue a free HTTPS certificate. Writing to /etc/nginx and running certbot needs root, so use your root login here. Your deploy user shouldn't need that power, and I'd keep it that way. scp nginx-<website-url>.conf root@<vps-ip>:/etc/nginx/sites-available/<website-url> ssh root@<vps-ip> "ln -s /etc/nginx/sites-available/<website-url> /etc/nginx/sites-enabled/ && nginx -t && systemctl reload nginx" # once DNS has propagated ssh root@<vps-ip> "certbot --nginx -d <website-url>" Certbot rewrites the site file in place and adds the 443 block plus an 80 → 443 redirect. Your production superuser The dev account was never created here (thanks PB_ENV), so nobody can log in yet: ssh <username>@<vps-ip> -i ~/.ssh/<keyname> cd /opt/<dirname> # confirm the container is running docker compose ps # create the real superuser with a real password docker compose exec pocketbase /pb/pocketbase superuser upsert <superuser-email> <strong-password> Visit https://<website-url>/_/ and log in. Here's what we've achieved so far: - ✅ PocketBase running on a server, in a container, only reachable through nginx - ✅ a deploy user with its own key - ✅ a real superuser and no dev account in production 5. A container registry Right now the server builds the image itself. It works, but every deploy means sending files over and building on a small machine, and there's no clean record of which version is running. What's a container registry? It's a place to store built Docker images, with tags. Instead of building on the server, we build once somewhere else, push the image to the registry with a version tag, and the server just pulls that exact image. That gets us: - Faster, lighter deploys. The server only downloads, it never builds - Versioning. We tag each image with the git commit that produced it, so you always know what's running and can <mark>roll back by pulling an older tag</mark> - The same image everywhere. What you tested is what you deploy What else could you use? - Docker Hub, the default and the most common, but anonymous pulls are rate limited - A cloud provider's own registry (AWS ECR, DigitalOcean Container Registry, and so on), good if you already live there, but another service to set up and pay attention to - Running your own registry, which is one more server to look after Why GHCR? GitHub Container Registry (ghcr.io) lives right next to the code. Our deploy will run on GitHub Actions anyway, and Actions can log in to GHCR with the token it already has, no extra account and no extra secret to manage. Your compose file changes in one place: swap build: . for an image: line. This is the file that lives on the server from now on: # pocketbase/docker-compose.yml services: pocketbase: image: ghcr.io/<github-user>/<dirname>:${IMAGE_TAG:-latest} restart: unless-stopped ports: - "127.0.0.1:8091:8090" environment: - PB_ENV=production volumes: - pb_data:/pb/pb_data volumes: pb_data: Two notes. Image names on GHCR have to be lowercase. And ${IMAGE_TAG:-latest} means "use this tag, or latest if I didn't give one", which is how a deploy picks a specific version. 6. Automate it with GitHub Actions You could build and push by hand, but we've built all this so we can just push to main and let the computer handle it automatically (AI, ladies and gentlemen). Here's the whole pipeline at a glance: !Diagram: pushing to main triggers GitHub Actions, which builds the image and pushes it to GHCR. The VPS pulls the image and runs compose up, then a health check decides whether the deploy passes or fails. First, give GitHub the key it needs. In your repo settings, add these as Actions secrets: - VPS_HOST = the VPS's IP - VPS_USER = <username> - VPS_SSH_KEY = the private key that matches the authorized key on the VPS - VPS_SSH_PASSPHRASE = the passphrase, only if you set one Then the workflow. It builds the image, pushes it to GHCR, copies the compose file to the server, pulls and restarts, and waits for PocketBase to say it's healthy. # .github/workflows/deploy-pocketbase.yml name: Deploy PocketBase on: push: branches: [main] paths: - "pocketbase/**" - ".github/workflows/deploy-pocketbase.yml" workflow_dispatch: {} permissions: contents: read packages: write jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Login to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push image uses: docker/build-push-action@v6 with: context: ./pocketbase file: ./pocketbase/Dockerfile push: true tags: | ghcr.io/<github-user>/<dirname>:${{ github.sha }} ghcr.io/<github-user>/<dirname>:latest - name: Copy compose file to VPS uses: appleboy/scp-action@v0.1.7 with: host: ${{ secrets.VPS_HOST }} username: ${{ secrets.VPS_USER }} key: ${{ secrets.VPS_SSH_KEY }} source: "pocketbase/docker-compose.yml" target: "/opt/<dirname>/" strip_components: 1 - name: Deploy to VPS uses: appleboy/ssh-action@v1.0.3 env: GHCR_ACTOR: ${{ github.actor }} GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} IMAGE_TAG: ${{ github.sha }} with: host: ${{ secrets.VPS_HOST }} username: ${{ secrets.VPS_USER }} key: ${{ secrets.VPS_SSH_KEY }} envs: GHCR_ACTOR,GHCR_TOKEN,IMAGE_TAG script: | echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_ACTOR" --password-stdin cd /opt/<dirname> docker compose pull pocketbase docker compose up -d - name: Health check uses: appleboy/ssh-action@v1.0.3 with: host: ${{ secrets.VPS_HOST }} username: ${{ secrets.VPS_USER }} key: ${{ secrets.VPS_SSH_KEY }} script: | for i in $(seq 1 30); do if curl -sf http://127.0.0.1:8091/api/health > /dev/null 2>&1; then echo "Health check passed" exit 0 fi echo "Attempt $i/30 - waiting..." sleep 2 done echo "Health check failed after 30 attempts" exit 1 Because of workflow_dispatch, you can also trigger it by hand from the Actions tab, no merging required. Very handy for the "I changed nothing, please just try again" moments. Push to main and watch the Actions tab do its thing. Every deploy is now tagged with its commit, and that's your rollback button. Finishing touches Point your frontend at it. For me that's an environment variable in Vercel: NEXT_PUBLIC_POCKETBASE_URL=https://<website-url> Redeploy the frontend, and that's it. For the days when something is off, here's my "turn it off and on again" toolkit: ssh <username>@<vps-ip> -i ~/.ssh/<keyname> cd /opt/<dirname> docker compose ps # status docker compose logs -f # tail the logs docker compose up -d # start/restart (does NOT pull a new image) docker compose pull && docker compose up -d # get the latest image, then restart docker compose exec pocketbase sh # shell into the running container What I left out, because it's beyond a one hour setup but you'll want it soon: backups of the pb_data volume, a firewall that only allows SSH, HTTP and HTTPS, and some kind of uptime monitoring. Here's the whole journey: - ✅ PocketBase running locally with a dev-only user - ✅ nginx in front of it, and why - ✅ a container that ships your code and keeps your data in a volume - ✅ a VPS with a domain, HTTPS and rate limiting - ✅ a registry so every deploy has a version - ✅ a pipeline that deploys on every push to main And there you have it! This was how to <mark>deploy</mark> your own PocketBase backend to a <mark>remote environment</mark>, in less than an hour (or something like that). <h6>P.S. if you only need this for a hobby project, you could skip the pipeline and the registry and stop at step 4. It works fine, you'll just be doing the deploys by hand.</h6> <h6>If you have any comments, questions, or concerns, please keep them to yourself.</h6> <h6>All typos are on purpose.</h6> Resources - PocketBase Docs - Unpaid Intern - Background Music - Shameless Plug

  8. Built in Go, it delivers insane performance while staying lightning-fast and lightweight to deploy anywhere. 🔖 Bookmark this before you lose it. Would you self-host this or pay SaaS pricing? Drop your take 👇 🔗... 🔗 pocketbase/pocketbase

    3 likes · 16 views

Latest stargazers

The last 10 distinct accounts we observed this year.

Most active participants

PR, issue, comment & review events in the past year.

Packages

No packages found.

README badges

Embed a badge in your README or website.

  • pocketbase/pocketbase reached #1 in daily stars on gitwal · Sep 14, 2022
  • pocketbase/pocketbase reached #2 in weekly stars on gitwal · Week of Jul 4, 2022
Live mentions · last 30 days
pocketbase/pocketbase mentions in the last 30 days