SurrealDB Multi-Model Database Installation

SurrealDB is a cloud-native multi-model database that combines relational, document, graph, and real-time capabilities into a single engine, with a SQL-like query language called SurrealQL. This guide covers installing SurrealDB on Linux, managing namespaces and databases, executing document and graph queries, using real-time subscriptions, and deploying with Docker.

Prerequisites

  • Ubuntu 20.04/22.04 or CentOS 8/Rocky Linux 8+
  • At least 1 GB RAM
  • Root or sudo access
  • Port 8000 available
  • curl installed

Install SurrealDB

Linux install script (recommended):

# Install the latest stable version
curl -sSf https://install.surrealdb.com | sh

# Verify installation
surreal version

# Move to system path if not already
sudo mv ~/.surreal/bin/surreal /usr/local/bin/
surreal version
# surreal 1.x.x for linux on amd64

Manual binary installation:

# Download the binary directly
SURREAL_VERSION="v2.0.0"
wget "https://github.com/surrealdb/surrealdb/releases/download/${SURREAL_VERSION}/surreal-${SURREAL_VERSION}.linux-amd64.tgz"
tar -xzf "surreal-${SURREAL_VERSION}.linux-amd64.tgz"
sudo mv surreal /usr/local/bin/
surreal version

Homebrew (macOS/Linux):

brew tap surrealdb/tap
brew install surrealdb/tap/surreal

Start SurrealDB Server

# Start with in-memory storage (development)
surreal start --log trace --user root --pass root memory

# Start with file-based storage (persistent, single node)
surreal start \
  --log info \
  --user root \
  --pass rootpassword \
  --bind 0.0.0.0:8000 \
  file:///var/lib/surrealdb/data.db

# Start with RocksDB backend (better for larger datasets)
surreal start \
  --log info \
  --user root \
  --pass rootpassword \
  --bind 0.0.0.0:8000 \
  rocksdb:///var/lib/surrealdb/data

# Create systemd service
sudo useradd -r -d /var/lib/surrealdb -s /bin/false surrealdb
sudo mkdir -p /var/lib/surrealdb
sudo chown surrealdb:surrealdb /var/lib/surrealdb

cat > /etc/systemd/system/surrealdb.service << 'EOF'
[Unit]
Description=SurrealDB Multi-Model Database
After=network.target

[Service]
Type=simple
User=surrealdb
ExecStart=/usr/local/bin/surreal start \
  --log info \
  --user root \
  --pass rootpassword \
  --bind 0.0.0.0:8000 \
  rocksdb:///var/lib/surrealdb/data
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now surrealdb

Access the SurrealDB CLI:

# Connect to the server
surreal sql --conn http://localhost:8000 --user root --pass rootpassword --ns myns --db mydb --pretty

# Or use the HTTP API
curl http://localhost:8000/health
# OK

Namespaces and Databases

SurrealDB organizes data into namespaces > databases > tables:

-- Connect to root and create a namespace
DEFINE NAMESPACE myapp;

-- Create a database within the namespace
USE NS myapp;
DEFINE DATABASE production;
USE DB production;

-- Create a table with schema definition
DEFINE TABLE user SCHEMAFULL;

DEFINE FIELD name ON TABLE user TYPE string ASSERT $value != NONE;
DEFINE FIELD email ON TABLE user TYPE string ASSERT $value != NONE AND string::is::email($value);
DEFINE FIELD created_at ON TABLE user TYPE datetime VALUE time::now();
DEFINE FIELD updated_at ON TABLE user TYPE datetime VALUE time::now();

-- Create an index
DEFINE INDEX email_idx ON TABLE user COLUMNS email UNIQUE;

-- Create a schemaless table (flexible documents, no schema enforcement)
DEFINE TABLE event SCHEMALESS;

SurrealQL: Document Queries

-- Insert records
CREATE user SET
    name = 'Alice Smith',
    email = '[email protected]';

-- Insert with explicit ID
CREATE user:alice SET
    name = 'Alice Smith',
    email = '[email protected]',
    role = 'admin';

-- Insert multiple records
INSERT INTO user [
    { name: 'Bob Jones', email: '[email protected]' },
    { name: 'Carol White', email: '[email protected]', role: 'editor' }
];

-- Select all users
SELECT * FROM user;

-- Select with conditions
SELECT * FROM user WHERE role = 'admin';

-- Select with nested field access
SELECT name, email, address.city FROM user WHERE address.country = 'US';

-- Update a record
UPDATE user:alice SET role = 'superadmin', updated_at = time::now();

-- Upsert (update or create)
UPDATE user:dave MERGE {
    name: 'Dave Brown',
    email: '[email protected]'
};

-- Delete a record
DELETE user:alice;

-- Delete with condition
DELETE user WHERE role = 'guest' AND created_at < time::now() - 30d;

-- Count records
SELECT count() FROM user GROUP ALL;

-- Aggregation
SELECT role, count() AS total FROM user GROUP BY role;

Graph Queries and Relationships

SurrealDB's graph capabilities use relation tables:

-- Create person records
CREATE person:alice SET name = 'Alice';
CREATE person:bob SET name = 'Bob';
CREATE person:carol SET name = 'Carol';

-- Create a graph relationship (alice KNOWS bob)
RELATE person:alice->knows->person:bob SET since = '2020-01-01';
RELATE person:alice->knows->person:carol SET since = '2021-06-15';
RELATE person:bob->knows->person:carol SET since = '2022-03-10';

-- Graph traversal: who does alice know?
SELECT ->knows->person AS friends FROM person:alice;

-- Traverse in reverse: who knows alice?
SELECT <-knows<-person AS known_by FROM person:alice;

-- Two-hop traversal: friends of friends
SELECT ->knows->person->knows->person AS friends_of_friends
FROM person:alice;

-- Get relationship metadata
SELECT ->(knows WHERE since > '2021-01-01')->person AS recent_friends
FROM person:alice;

-- Product and purchase graph
CREATE product:laptop SET name = 'Laptop Pro', price = 1299.99;
CREATE product:mouse SET name = 'Wireless Mouse', price = 49.99;
CREATE customer:user1 SET name = 'Alice', email = '[email protected]';

RELATE customer:user1->purchased->product:laptop SET
    quantity = 1,
    purchased_at = time::now();

RELATE customer:user1->purchased->product:mouse SET
    quantity = 2,
    purchased_at = time::now();

-- Get all products purchased by user1
SELECT ->purchased->product.* FROM customer:user1;

-- Get total spend per customer
SELECT
    <-purchased<-customer AS customer,
    math::sum(<-purchased.quantity * price) AS total_revenue
FROM product;

Real-Time Subscriptions

SurrealDB supports live queries via WebSocket:

// Node.js with surrealdb.js
// npm install surrealdb.js

import Surreal from 'surrealdb.js';

const db = new Surreal();

async function main() {
    await db.connect('http://localhost:8000/rpc');
    await db.signin({ user: 'root', pass: 'rootpassword' });
    await db.use({ ns: 'myapp', db: 'production' });

    // Live query - receives updates in real-time
    const queryUuid = await db.live('user', (action, result) => {
        console.log(`Action: ${action}`, result);
        // action: CREATE, UPDATE, or DELETE
    });

    // Insert a record - the live query callback fires automatically
    await db.create('user', { name: 'Test User', email: '[email protected]' });

    // Wait 2 seconds, then unsubscribe
    setTimeout(async () => {
        await db.kill(queryUuid);
        await db.close();
    }, 2000);
}

main();
# Live query via CLI (SurrealQL)
# In the CLI:
LIVE SELECT * FROM user;

# This returns a UUID, and subsequent changes will stream to your session
# To kill the live query:
KILL 'uuid-from-live-query';

Authentication and Access Control

-- Create a namespace-level user
DEFINE USER nsadmin ON NAMESPACE myapp
    PASSWORD 'nspassword'
    ROLES OWNER;

-- Create a database-level user
DEFINE USER dbuser ON DATABASE
    PASSWORD 'dbpassword'
    ROLES EDITOR;

-- Define access methods for end users (JWT-based)
DEFINE ACCESS user_jwt ON DATABASE TYPE JWT
    ALGORITHM HS512
    KEY 'your-secret-jwt-key-at-least-32-chars-long'
    SESSION 24h;

-- Scope-based access (application users with row-level security)
DEFINE ACCESS user_account ON DATABASE TYPE RECORD
    SIGNUP (
        CREATE user SET
            email = $email,
            password = crypto::argon2::generate($password),
            created_at = time::now()
    )
    SIGNIN (
        SELECT * FROM user
        WHERE email = $email
        AND crypto::argon2::compare(password, $password)
    )
    SESSION 7d;

-- Row-level permissions
DEFINE TABLE post SCHEMAFULL PERMISSIONS
    FOR select WHERE author = $auth.id OR visibility = 'public'
    FOR create, update WHERE author = $auth.id
    FOR delete WHERE author = $auth.id;

Docker Deployment

# Single-node Docker deployment
docker run -d \
  --name surrealdb \
  -p 8000:8000 \
  -v surrealdb_data:/data \
  surrealdb/surrealdb:latest start \
  --log info \
  --user root \
  --pass rootpassword \
  /data

# Docker Compose
cat > /opt/surrealdb/docker-compose.yml << 'EOF'
version: '3.7'
services:
  surrealdb:
    image: surrealdb/surrealdb:latest
    command: start --log info --user root --pass ${SURREAL_PASS} /data
    ports:
      - "8000:8000"
    volumes:
      - surrealdb_data:/data
    environment:
      - SURREAL_PASS=rootpassword
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

volumes:
  surrealdb_data:
EOF

cd /opt/surrealdb && docker-compose up -d

Troubleshooting

Connection refused on port 8000:

# Check if SurrealDB is running
sudo systemctl status surrealdb
# Or for Docker:
docker ps | grep surrealdb

# Check listening port
ss -tlnp | grep 8000

# Test health endpoint
curl http://localhost:8000/health

Authentication failed:

# Verify credentials in the service unit file
sudo cat /etc/systemd/system/surrealdb.service | grep pass

# Test authentication
curl -X POST http://localhost:8000/sql \
  -H "Accept: application/json" \
  -H "NS: myapp" \
  -H "DB: production" \
  -u "root:rootpassword" \
  -d "SELECT * FROM user LIMIT 1;"

Query syntax error:

# SurrealQL is case-insensitive for keywords
# Table names are case-sensitive
# IDs use colon notation: tablename:id

# Check the SurrealDB SQL docs for syntax differences from standard SQL
# RELATE uses ->edge->target format (not JOIN)
# Record IDs are returned as: { id: { tb: "user", id: "alice" } }

# Enable trace logging for debugging
surreal start --log trace memory

Data not persisting between restarts:

# Ensure using file or rocksdb backend, not memory
# memory is ephemeral - data is lost on restart

surreal start --log info --user root --pass pass file:///var/lib/surrealdb/data.db

# Verify data file exists
ls -lh /var/lib/surrealdb/

Conclusion

SurrealDB unifies relational, document, graph, and real-time data access in a single database with an expressive query language. Its flexible schema options, built-in authentication scopes, and live query subscriptions make it a compelling choice for modern web and IoT applications. Deploy it with Docker for fast setup, use RocksDB storage for production persistence, and leverage graph relations for social, recommendation, or knowledge graph use cases without additional infrastructure.