Switching clouds? Get up to $10K in credits + hands-on help.

Apply now
Platform

Host PocketBase on Render

Deploying a SQLite-backed service on Render: concepts and patterns

PocketBase is an open-source backend written in Go that provides a SQLite database, authentication, file storage, real-time subscriptions, and an admin UI, all bundled into a single executable binary. This article explains how and why the deployment pattern works when you host PocketBase on Render, first as a standalone backend and then as the data layer behind a Next.js frontend. The examples here are simplified illustrations that you'll adapt to your own project's requirements. By the end, you'll understand the architectural decisions behind deploying a SQLite-backed service on Render, a pattern applicable well beyond PocketBase.

Why PocketBase's architecture matters for deployment

PocketBase is a single-binary backend-as-a-service. One process serves the REST and real-time APIs, runs the admin dashboard, manages authentication, handles file uploads, and reads from and writes to an embedded SQLite database.

The single-binary model simplifies what you deploy, but it introduces a critical constraint: SQLite stores all data as files on the local filesystem. If the filesystem is ephemeral (as it is by default on most managed cloud platforms), every redeployment or restart destroys your database and uploaded files. The deployment challenge isn't "how do I run a binary" but "how do I ensure the filesystem it writes to survives across deploys."

On Render, the answer is a persistent disk: a filesystem volume that you attach to a web service at a specified mount path. Data written to that path persists across deploys and restarts. For PocketBase, mount the disk at the path where PocketBase stores its pb_data directory, the location that holds the SQLite database files, migration records, and uploaded assets. Without the disk, your service appears to work on first deploy but loses all state the moment Render provisions a new instance.

Any application that writes state to the local filesystem rather than to an external database or object store needs the same treatment on Render.

Project structure and Dockerfile

A Dockerfile gives you explicit, version-controlled authority over what happens at build time versus runtime. For PocketBase, the build stage fetches the pre-compiled binary and prepares the filesystem layout. The run stage starts the binary with flags that point to the persistent mount path and bind to the port Render expects.

The --dir flag points PocketBase's data directory to /pb/pb_data, the path where you'll mount the persistent disk. The --http=0.0.0.0:${PORT:-10000} pattern binds to all interfaces on Render's expected port (default 10000). Render injects a PORT environment variable at runtime; the shell form above keeps PocketBase aligned with that value if you change the port in service settings.

Pin PocketBase to a specific version so builds are deterministic. Check the PocketBase releases page for the current tag before you copy this snippet.

Configuring the Render web service

Render needs three things to run your PocketBase container: the service definition, the port contract, and a persistent disk.

When you create a web service on Render, you connect it to a Git repository containing your Dockerfile. Each git push to the linked branch triggers a new build-and-deploy cycle.

Port binding is the handshake between your service and Render's routing layer. Your process must bind to host 0.0.0.0 (not 127.0.0.1), or Render cannot reach it. The default expected port is 10000, and Render can usually detect another open port if you bind elsewhere. Binding only to localhost is the failure mode that keeps the service from becoming routable.

Health checks determine whether your service is ready to receive traffic. Render's default is a TCP probe to your open port, which works for PocketBase out of the box. Optionally set healthCheckPath: /api/health in render.yaml or the Dashboard for an HTTP check against PocketBase's health endpoint.

The persistent disk configuration requires a name and a mount path. Set the mount path to /pb/pb_data, the same path passed to PocketBase's --dir flag. You can configure the disk either through the Render Dashboard or declaratively via a render.yaml file:

Persistent disks are billed based on provisioned size. Review Render's pricing page to choose an appropriate size. You can increase a disk's size after creation, but you can't decrease it, so start conservatively.

Extending to a full-stack app: Next.js and PocketBase

The single-service pattern above is the foundation for a common full-stack setup: a Next.js frontend backed by PocketBase. Next.js and PocketBase run as separate services on Render because they're independent runtime processes with different build pipelines, start commands, and resource requirements. Next.js is a Node.js application serving your frontend and API routes. PocketBase is the Go binary you configured above.

On Render, Next.js runs as a public web service with an HTTPS URL. PocketBase typically runs as a private service so its API and admin UI stay off the public internet while Next.js calls it over Render's private network. If you need the admin dashboard reachable from a browser without routing through Next.js, deploy PocketBase as a public web service instead. Note that as a private service, PocketBase only receives Render's default TCP health checks; the HTTP healthCheckPath option from the standalone setup applies only when PocketBase runs as a web service. The request flow is:

The browser talks only to Next.js. Next.js makes server-side requests to PocketBase using PocketBase's internal Render hostname, and PocketBase reads and writes its SQLite database on the attached disk.

Connecting Next.js to PocketBase with environment variables

Environment variables are how Next.js locates PocketBase without hardcoding an address. Store PocketBase's internal host and port in a variable and read it in your server-side code. The variable is named POCKETBASE_HOSTPORT because Render's hostport property returns a combined host:port value (for example pocketbase-service-ab1c:10000):

The same code runs locally or on Render by changing only the variable. Replace any localhost reference with PocketBase's internal hostname (private service) or its public onrender.com URL (public web service).

Wiring both services with render.yaml

Infrastructure as Code lets you declare both services, their env vars, and the disk in one file:

The Next.js service uses fromService with property: hostport so Render injects PocketBase's internal hostname and port together (for example pocketbase-service-ab1c:10000).

The PocketBase service uses a community-maintained container image, pinned to ghcr.io/muchobien/pocketbase:0.39.6 (avoid :latest in production). Because this image's tags are maintained independently of PocketBase and can lag official releases, confirm the tag actually exists in the registry before deploying, or pin to an image digest—a missing tag causes an Image Pull Failed. This pinned version also won't automatically match the v0.39.6 you build from the Dockerfile earlier, and image-backed services don't auto-redeploy when a new image is pushed to the tag; you trigger deploys manually from the Dashboard or a deploy hook.

The disk is mounted at /data, and PocketBase writes under /data/pb_data. (This differs from the Dockerfile setup earlier, which mounts at /pb/pb_data; either path works as long as --dir sits inside the mount path.) One subtlety: this image ships its own ENTRYPOINT script that already invokes the pocketbase binary, and Render's dockerCommand overrides the image's CMD, not its ENTRYPOINT—so the command is passed as arguments to that script. That's why it starts with serve rather than pocketbase serve: the entrypoint prepends the binary for you, and adding pocketbase would run pocketbase pocketbase serve … and fail to start. If you'd rather not depend on the wrapper script, use the native Dockerfile from the Project structure and Dockerfile section above. Make sure the --dir path stays inside the disk mount path, or PocketBase writes to the ephemeral filesystem. If you use a monorepo, set the root directory for the Next.js service.

The deployment pattern in summary

The pattern reduces to four architectural decisions: package the binary in a Dockerfile, bind to Render's expected port, mount a persistent disk at PocketBase's data directory, and manage configuration through environment variables. Each decision maps to a general principle (containerized build control, port-based service contracts, persistent storage for stateful workloads, and externalized configuration) that extends to any service you deploy on this platform.

This pattern fits internal tools and production workloads with modest traffic. Adding a Next.js frontend extends it to two coordinated services connected over the private network, and the same principles (persistent storage, port contracts, environment-based discovery) still apply. As your requirements evolve, you can add external object storage for uploads or migrate to a multi-service stack (for example an API backed by Render Postgres) when PocketBase's single-instance model no longer fits.

Frequently asked questions