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
terminusdbservice 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
curlandpython3on your workstation for the examples below
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.
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 StandardOpen 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 1002Connect to your VM
Connect over SSH as azureuser:
ssh azureuser@<public-ip>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 nginxAll 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'Check what the network can reach
List the listening ports:
sudo ss -Hltn | awk '{print $4}' | sort -uOnly 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.shRetrieve 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.txtThe 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-)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/infoThe 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/infoEach 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.toolTo 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.shCreate 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.toolAdd 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"}'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"}'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 -30Branch 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.toolQuery 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 -20Call 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/infoThe 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())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 nginxBack 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 terminusdbTo 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 terminusdbKeep the VM patched
unattended-upgrades applies Ubuntu security updates automatically. To apply updates immediately, run:
sudo apt-get update && sudo apt-get -y upgradeChange 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 NEWPWConfirm that the change took effect and that the appliance still refuses everything else:
sudo /usr/local/sbin/terminusdb-verify-auth.shTroubleshooting
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.txtThe service will not start. Check the database and the proxy separately:
sudo systemctl status terminusdb --no-pager -l | head -20sudo 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.shSupport
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