Back to Blog
Tutorial9 min read2026-07-18

How to Deploy SurrealDB on PandaStack (2026)

SurrealDB is a multi-model database that speaks SQL-ish, graph, and document all at once. Here's how to run a persistent, production SurrealDB instance on PandaStack with a real backing volume — not the in-memory demo everyone accidentally ships.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

SurrealDB (https://surrealdb.com) is one of those databases that sounds too good until you actually use it, then it's just genuinely handy: relational tables, graph edges, document nesting, and a built-in auth/permissions layer, all behind one query language (SurrealQL) and one binary. The catch that bites people is that the quickstart command runs memory as the storage engine, everyone deploys that, and then a restart eats the entire database. Let's not do that.

This is an end-to-end guide to running SurrealDB as a real, persistent container on PandaStack — with a durable volume, a proper root credential, and an HTTP health check.

Why run SurrealDB as a container (not a managed DB)

PandaStack's managed databases cover PostgreSQL, MySQL, Redis, and MongoDB. SurrealDB isn't in that managed list, so you run it as a container app with an attached persistent volume. That's totally fine — SurrealDB ships as a single static binary in an official image, and it's designed to be self-hosted. You just have to own the storage decision yourself, which is the whole point of this post.

Step 1: Pick a storage engine that survives a restart

SurrealDB supports several backends. For a single-node deployment the pragmatic choice is the embedded RocksDB engine, pointed at a file path on a persistent volume:

surreal start rocksdb:/data/surreal.db

memory: is for tests. tikv: is for distributed clusters and is overkill until you have a real scaling story. RocksDB on a mounted volume gives you durability with zero extra infrastructure.

Step 2: The Dockerfile

You don't strictly need a custom image — the official surrealdb/surrealdb image works — but wrapping it makes the start command explicit and reviewable in Git:

FROM surrealdb/surrealdb:latest

# Data lives on a mounted volume at /data (see step 4)
# Root creds come from env vars, never baked into the image
EXPOSE 8000

ENTRYPOINT ["/surreal", "start", \
  "--bind", "0.0.0.0:8000", \
  "--user", "$SURREAL_USER", \
  "--pass", "$SURREAL_PASS", \
  "rocksdb:/data/surreal.db"]

One gotcha: the array form of ENTRYPOINT does not expand $SURREAL_USER. Use a shell entrypoint so env vars are substituted at runtime:

FROM surrealdb/surrealdb:latest
EXPOSE 8000
COPY start.sh /start.sh
ENTRYPOINT ["/bin/sh", "/start.sh"]
# start.sh
#!/bin/sh
exec /surreal start \
  --bind 0.0.0.0:8000 \
  --user "$SURREAL_USER" \
  --pass "$SURREAL_PASS" \
  rocksdb:/data/surreal.db

Step 3: Deploy the container

  1. 1Push the repo (Dockerfile + start.sh) to GitHub.
  2. 2In the dashboard (https://dashboard.pandastack.io): New App → Container App, connect the repo. PandaStack builds the image with rootless BuildKit and deploys.
  3. 3Set the container port to 8000.

Or from the CLI:

npm install -g @pandastack/cli
panda login
panda deploy

Step 4: Attach a persistent volume

This is the step that separates a real database from a disappearing one. In the app settings, add a persistent volume mounted at /data. Everything under that path survives restarts, redeploys, and scaling events. Without it, rocksdb:/data/surreal.db writes to the container's ephemeral filesystem and vanishes on the next deploy — the exact failure mode we're avoiding.

Step 5: Set the root credentials as environment variables

In the app's Environment Variables panel:

VariableValue
SURREAL_USERroot (or your chosen admin user)
SURREAL_PASSa long random secret — openssl rand -hex 24

PandaStack encrypts these at rest and never prints them in build logs. Do not hardcode them in the Dockerfile.

Step 6: Connect and initialize

Once live, your instance answers at https://your-surreal.pandastack.io. SurrealDB speaks HTTP and WebSocket. Initialize a namespace and database, then define a table:

curl -X POST https://your-surreal.pandastack.io/sql \
  -u "root:$SURREAL_PASS" \
  -H "Accept: application/json" \
  -H "surreal-ns: app" -H "surreal-db: main" \
  --data-binary "DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD email ON user TYPE string ASSERT string::is::email(\$value);
DEFINE FIELD created ON user TYPE datetime VALUE time::now();
DEFINE INDEX email_idx ON user FIELDS email UNIQUE;"

From Node using the official SDK:

import { Surreal } from 'surrealdb'

const db = new Surreal()
await db.connect('https://your-surreal.pandastack.io/rpc')
await db.signin({ username: 'root', password: process.env.SURREAL_PASS })
await db.use({ namespace: 'app', database: 'main' })

const [created] = await db.create('user', { email: 'a@b.com' })
console.log(created)

Step 7: Lock it down before you put real data in it

Running as root over the public internet is a launch-week convenience, not a production posture. Before you store anything real:

  • Define scoped auth with DEFINE ACCESS (SurrealDB's record-level auth) so your app connects as a limited user, not root.
  • Set field-level PERMISSIONS clauses on tables so a leaked token can't read everything.
  • Consider putting the instance behind an edge function or your API rather than exposing /sql directly.

Step 8: Back it up

SurrealDB has a built-in export:

curl https://your-surreal.pandastack.io/export \
  -u "root:$SURREAL_PASS" \
  -H "surreal-ns: app" -H "surreal-db: main" > backup.surql

Run that from a scheduled PandaStack cronjob (a tiny container on 0 4 * * *) that writes the dump to object storage. Ten lines of shell, and you sleep better.

Troubleshooting

  • "Database is empty after redeploy" — you forgot the volume, or you're still on memory:. Check both.
  • Auth errors — the array-form ENTRYPOINT didn't expand your env vars; switch to the shell entrypoint from Step 2.
  • Slow first query after idle — free-tier apps scale to zero; a paid tier keeps the container warm.

Wrap-up

SurrealDB is a delight when it's persistent and locked down, and a footgun when it's memory: and root-over-the-internet. Container app + a /data volume + RocksDB + scoped auth gets you the good version. Docs: https://docs.pandastack.io. Start free at https://dashboard.pandastack.io.

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also