Deployment guide

TerminusDB

Overview

TerminusDB is an open-source document graph database that treats your data the way git treats code. You store JSON documents against a schema that describes how they relate, and the database keeps the full history. Every change is a commit. You can branch a database, diff two branches, reset to any earlier state, and merge work back together.

Queries are written in WOQL, TerminusDB's own query language. A complete HTTP API covers documents, schemas, branches, diffs and history. Client libraries are published for Python and JavaScript, and the community maintains an Elixir client.

This SecureAnyCloud image delivers TerminusDB 12.0.7 on Ubuntu 24.04, served over TLS, with a unique administrator password generated on the first boot of your VM.

What you get:

  • TerminusDB 12.0.7, run by systemd from the official container image pinned by tag and by manifest digest, as the unprivileged terminusdb service account
  • The complete HTTP API behind TLS on port 443, with a certificate generated for your VM at first boot
  • A durable store at /var/lib/terminusdb/storage, owned by the service account, which survives reboots
  • A per-VM administrator password written to /root/terminusdb-credentials.txt, readable only by root
  • On-VM self-tests that prove the security posture at any time
  • 24/7 SecureAnyCloud support

A note on the dashboard

Upstream discontinued the TerminusDB Dashboard component. The server still serves a page at /, but in 12.0.7 that page is a notice to that effect with a link to upstream's migration documentation. It is not a working console.

This image is therefore documented and supported as what it is: a database you drive from the HTTP API, the command line and the client libraries. Everything in this guide uses curl, so it works the same from any language.

Prerequisites

  • An Azure subscription
  • SSH access to the VM (port 22), plus HTTPS (port 443) from wherever you call the API
  • curl and python3 on your workstation for the examples below
1

Deploy from the Azure Marketplace

Search the Azure Marketplace for TerminusDB on Ubuntu 24.04 LTS by SecureAnyCloud. Choose Standard_B2s or larger, and allow inbound ports 22 and 443.

2

Deploy from the Azure CLI

# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:terminusdb-ubuntu-24-04:default:latest
az vm create \
  --resource-group my-resource-group \
  --name my-terminusdb \
  --image sac1647283583153:terminusdb-ubuntu-24-04:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

Open the ports the appliance uses:

az vm open-port --resource-group my-resource-group --name my-terminusdb --port 22 --priority 1001
az vm open-port --resource-group my-resource-group --name my-terminusdb --port 443 --priority 1002
3

Connect to your VM

Connect over SSH as azureuser:

ssh azureuser@<public-ip>
4

Confirm TerminusDB is running

Three units make up the appliance. docker runs the database container, terminusdb is the database itself, and nginx terminates TLS in front of it.

systemctl is-active docker terminusdb nginx

All three report active.

Confirm the exact image the appliance runs, pinned by digest:

sudo docker images terminusdb/terminusdb-server --digests --format 'table {{.Repository}}\t{{.Tag}}\t{{.Digest}}'

The health endpoint answers without a credential, so you can use it as a load-balancer or uptime probe:

curl -sk https://localhost/api/ok -w '\n'
5

Check what the network can reach

List the listening ports:

sudo ss -Hltn | awk '{print $4}' | sort -u

Only 22, 80 and 443 are bound on an address reachable from outside the VM. Port 80 only redirects to HTTPS. TerminusDB itself listens on 127.0.0.1:6363 and is reachable only through the TLS proxy, so the administrator password is never sent over a plaintext connection.

The appliance can verify this for you:

sudo /usr/local/sbin/terminusdb-port-check.sh
6

Retrieve the first-boot administrator password

TerminusDB's published default login is the user admin with the password root. This image never creates it. No database is initialised while the image is built, and the entire store is removed before the image is captured, so the image you launched contains no accounts at all.

On the first boot of your VM, a password is generated for that VM alone. Read it from the credentials file:

sudo cat /root/terminusdb-credentials.txt

The file is 0600 root:root and contains the user, the password and the URLs for this VM.

Store the password in a shell variable for the rest of this guide:

PW=$(sudo grep '^TERMINUSDB_ADMIN_PASSWORD=' /root/terminusdb-credentials.txt | cut -d= -f2-)
7

Confirm the API refuses everything but your password

By default, TerminusDB treats a caller with no credential as a built-in anonymous identity and reports its version and build hash to them. This image refuses unauthenticated requests at the edge instead. A request with no credential is rejected:

curl -sk -o /dev/null -w 'no credential: %{http_code}\n' https://localhost/api/info

The upstream default and a blank password are rejected too:

curl -sk -o /dev/null -w 'admin/root: %{http_code}\n' -u 'admin:root' https://localhost/api/info
curl -sk -o /dev/null -w 'blank: %{http_code}\n' -u 'admin:' https://localhost/api/info

Each returns 401. Your own password is accepted, and the response names the admin authority:

PW=$(sudo grep '^TERMINUSDB_ADMIN_PASSWORD=' /root/terminusdb-credentials.txt | cut -d= -f2-)
curl -sk -u "admin:$PW" https://localhost/api/info | python3 -m json.tool

To verify the whole security posture, run the following scripts. They also check that each of these tests fails against a known-bad input:

sudo /usr/local/sbin/terminusdb-verify-auth.sh
sudo /usr/local/sbin/terminusdb-gate-selftest.sh
8

Create a database

Create a database named people through the HTTP API:

PW=$(sudo grep '^TERMINUSDB_ADMIN_PASSWORD=' /root/terminusdb-credentials.txt | cut -d= -f2-)
curl -sk -u "admin:$PW" -X POST https://localhost/api/db/admin/people \
  -H 'Content-Type: application/json' \
  -d '{"label":"People","comment":"example database"}'

List the databases that exist:

PW=$(sudo grep '^TERMINUSDB_ADMIN_PASSWORD=' /root/terminusdb-credentials.txt | cut -d= -f2-)
curl -sk -u "admin:$PW" https://localhost/api/db | python3 -m json.tool
9

Add a schema

TerminusDB is schema-checked: documents must satisfy a class you define. Add an Employee class:

PW=$(sudo grep '^TERMINUSDB_ADMIN_PASSWORD=' /root/terminusdb-credentials.txt | cut -d= -f2-)
curl -sk -u "admin:$PW" -X POST \
  'https://localhost/api/document/admin/people?graph_type=schema&author=guide&message=add+Employee+schema' \
  -H 'Content-Type: application/json' \
  -d '{"@type":"Class","@id":"Employee","name":"xsd:string","title":"xsd:string"}'
10

Insert and read documents

Insert a document that matches the schema:

PW=$(sudo grep '^TERMINUSDB_ADMIN_PASSWORD=' /root/terminusdb-credentials.txt | cut -d= -f2-)
curl -sk -u "admin:$PW" -X POST \
  'https://localhost/api/document/admin/people?author=guide&message=insert+Ada+Lovelace' \
  -H 'Content-Type: application/json' \
  -d '{"@type":"Employee","name":"Ada Lovelace","title":"Analyst"}'

Read the document back:

PW=$(sudo grep '^TERMINUSDB_ADMIN_PASSWORD=' /root/terminusdb-credentials.txt | cut -d= -f2-)
curl -sk -u "admin:$PW" 'https://localhost/api/document/admin/people?type=Employee'

The schema is enforced. TerminusDB rejects a document with a property the class does not declare instead of storing it:

PW=$(sudo grep '^TERMINUSDB_ADMIN_PASSWORD=' /root/terminusdb-credentials.txt | cut -d= -f2-)
curl -sk -o /dev/null -w 'schema violation: %{http_code}\n' -u "admin:$PW" -X POST \
  'https://localhost/api/document/admin/people?author=guide&message=bad' \
  -H 'Content-Type: application/json' \
  -d '{"@type":"Employee","name":"Bad Record","nonexistent_property":"x"}'
11

Read the commit history

This is what makes TerminusDB different. Every write above was a commit, with the author and message you supplied. Read the log:

PW=$(sudo grep '^TERMINUSDB_ADMIN_PASSWORD=' /root/terminusdb-credentials.txt | cut -d= -f2-)
curl -sk -u "admin:$PW" 'https://localhost/api/log/admin/people' | python3 -m json.tool | head -30
12

Branch a database

Branching is cheap and works like git. Take a copy of main, change it independently, and compare.

PW=$(sudo grep '^TERMINUSDB_ADMIN_PASSWORD=' /root/terminusdb-credentials.txt | cut -d= -f2-)
curl -sk -u "admin:$PW" -X POST https://localhost/api/branch/admin/people/local/branch/trial \
  -H 'Content-Type: application/json' \
  -d '{"origin":"admin/people/local/branch/main"}'

Confirm that the branch exists:

PW=$(sudo grep '^TERMINUSDB_ADMIN_PASSWORD=' /root/terminusdb-credentials.txt | cut -d= -f2-)
curl -sk -u "admin:$PW" 'https://localhost/api/db/admin/people?branches=true' | python3 -m json.tool
13

Query with WOQL

WOQL is TerminusDB's query language. This query finds every document of type Employee:

PW=$(sudo grep '^TERMINUSDB_ADMIN_PASSWORD=' /root/terminusdb-credentials.txt | cut -d= -f2-)
curl -sk -u "admin:$PW" -X POST https://localhost/api/woql/admin/people \
  -H 'Content-Type: application/json' \
  -d '{"query":{"@type":"Triple","subject":{"variable":"S"},"predicate":{"node":"rdf:type"},"object":{"node":"@schema:Employee"}}}' \
  | python3 -m json.tool | head -20
14

Call the API from your workstation

Every command above used localhost from inside the VM. From elsewhere, use the VM's address. The certificate generated at first boot names that address, so once you trust it you can verify it normally:

curl -k -u "admin:<new-password>" https://<public-ip>/api/info

The Python client uses the same API:

from terminusdb_client import Client client = Client("https://<public-ip>") client.connect(user="admin", key="<admin-password>", team="admin", db="people") print(client.get_all_documents())
15

Use your own TLS certificate

The image generates a self-signed certificate for your VM's address. To use a certificate for a real domain, replace the certificate and key pair, then reload nginx:

sudo install -m 0644 -o root -g root /path/to/fullchain.pem /etc/terminusdb/tls/server.crt
sudo install -m 0640 -o root -g root /path/to/privkey.pem /etc/terminusdb/tls/server.key
sudo systemctl reload nginx
16

Back up and restore

The store is a directory, so a backup is a copy of it. Take the copy while the database is stopped:

sudo systemctl stop terminusdb
sudo tar -czf /root/terminusdb-backup.tar.gz -C /var/lib/terminusdb storage
sudo systemctl start terminusdb

To restore, put the directory back and correct its ownership:

sudo systemctl stop terminusdb
sudo tar -xzf /root/terminusdb-backup.tar.gz -C /var/lib/terminusdb
sudo chown -R terminusdb:terminusdb /var/lib/terminusdb/storage
sudo systemctl start terminusdb
17

Keep the VM patched

unattended-upgrades applies Ubuntu security updates automatically. To apply updates immediately, run:

sudo apt-get update && sudo apt-get -y upgrade
18

Change the administrator password

The command below reads the new password from standard input, so it never appears in your shell history or the process list.

Do not pipe an empty value into it. TerminusDB accepts an empty password, and your instance would then authenticate anyone. The block refuses any password shorter than 12 characters instead of passing it on:

NEWPW='<new-password>' if [ "${#NEWPW}" -lt 12 ]; then echo "Refusing: the password must be at least 12 characters. Nothing was changed." else printf '%s\n' "$NEWPW" | sudo docker exec -i terminusdb /app/terminusdb/terminusdb user password admin
sudo sed -i "s|^TERMINUSDB_ADMIN_PASSWORD=.*|TERMINUSDB_ADMIN_PASSWORD=${NEWPW}|" /root/terminusdb-credentials.txt echo "Updated, and /root/terminusdb-credentials.txt now matches." fi unset NEWPW

Confirm that the change took effect and that the appliance still refuses everything else:

sudo /usr/local/sbin/terminusdb-verify-auth.sh

Troubleshooting

The API returns 401 with the password from the credentials file. Confirm that you are reading the current file. If the VM was re-provisioned, the password was rotated:

sudo grep '^TERMINUSDB_ADMIN_USER=' /root/terminusdb-credentials.txt

The service will not start. Check the database and the proxy separately:

sudo systemctl status terminusdb --no-pager -l | head -20
sudo journalctl -u terminusdb -n 15 --no-pager -p err --since '-10min'

To confirm the whole appliance is healthy, run the self-test:

sudo /usr/local/sbin/terminusdb-selftest.sh

Support

SecureAnyCloud publishes this image with 24/7 support for deployment, upgrades, TLS certificates and custom domains, client library integration, schema design and scaling.

TerminusDB is a trademark of its respective owner. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.

Deploy on Azure

Launch TerminusDB 12.0.7 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure