Deployment guide

Dgraph

Overview

This guide shows you how to deploy and use the SecureAnyCloud Dgraph 25.5.1 image on Microsoft Azure. Dgraph is a distributed, horizontally scalable, native graph database. It stores data as nodes and typed edges, and it answers deep graph traversals plus full-text, regular-expression, geo and vector-indexed queries.

Dgraph speaks two query languages: DQL (Dgraph Query Language) over HTTP and gRPC, and GraphQL, generated from a schema you upload. One dgraph binary runs two services. Zero coordinates the cluster, and Alpha stores the data and serves queries. Dgraph is licensed under the Apache License, Version 2.0. Since version 25, its access control lists (ACL), multi-tenancy, backup and restore features are open source too.

What you get:

  • No default login. Dgraph ACL is switched on. On first boot, the image generates a unique 32-character password for the groot administrator. It also generates a unique 256-bit key that signs Dgraph's access tokens. Dgraph's documented default (groot / password) is changed before the API is reachable from the network, and it is refused from then on. Dgraph reads the key from a file, never from a command line.
  • Nothing runs without a login. Dgraph runs with anonymous=none. A caller who has not logged in may call only /login and /health. Every query, mutation, schema change, GraphQL request and admin call needs an access token.
  • HTTPS from first boot. nginx serves the Dgraph HTTP and GraphQL APIs on port 443 with a certificate generated on the VM for its own address. Port 80 only redirects to HTTPS, and requests for any other hostname are dropped.
  • Loopback-only Dgraph ports. Zero (5080, 6080) and Alpha (7080, 8080, 9080) listen on 127.0.0.1. Only SSH, 80 and 443 are reachable. nginx publishes only the API paths, so Dgraph's /debug profiling endpoints and Zero's admin endpoints are never exposed.
  • Sandboxed services. Zero and Alpha run as an unprivileged dgraph user in a systemd sandbox. The sandbox can write only /var/lib/dgraph. It cannot read system secrets such as /etc/shadow, the credentials file or the TLS key. It also cannot reach the Azure Instance Metadata Service or the Azure wire server. Exports and backups can therefore be written only under /var/lib/dgraph.
  • A fully patched Ubuntu 24.04 base at build time, with per-VM secrets and the SecureAnyCloud security defaults.

The image contains Ubuntu 24.04 LTS, Dgraph 25.5.1 (the official release binary, checksum-verified) in /opt/dgraph, nginx, and the SecureAnyCloud first-boot service. All Dgraph data lives on a dedicated 30 GiB data disk at /var/lib/dgraph. First boot also loads a tiny demo graph of three Person nodes joined by friend edges, so your first query returns data. A swap file is created on each boot on the VM's temporary resource disk (/mnt), never on the OS disk.

Coming from the earlier image? Earlier versions of this offer (Dgraph 25.3.8) used nginx basic authentication and an admin token. If your VM has /root/dgraph-info.txt instead of /root/dgraph-credentials.txt, it was deployed from that image. Its basic authentication user, password and admin token are in that file. Such a VM keeps its design until you replace it. To migrate, deploy a new VM from this image, export your data from the old VM and import it into the new one.

Prerequisites

  • An Azure subscription and permission to create virtual machines.
  • An SSH key pair.
  • A network security group that allows TCP 22 from your own address. It must also allow TCP 443 from the clients that will use the API, and optionally TCP 80, which only redirects. Ports 5080, 6080, 7080, 8080 and 9080 are never exposed because Dgraph listens on them only on loopback.

Recommended VM size: Standard_B2s (2 vCPU, 4 GiB) for evaluation and small graphs. Use Standard_D4s_v5 or larger for production. Dgraph Alpha keeps hot data in memory, so choose more memory for bigger graphs. Resize the data disk to suit your dataset.

1

Deploy from the Azure Portal

Search the Azure Marketplace for Dgraph on Ubuntu 24.04 and choose Create. Pick your VM size and SSH key.

Attach a network security group that allows TCP 22 from your management network. Allow TCP 443 from the networks and applications that will query Dgraph. Port 80 only issues an HTTPS redirect.

Azure creates the data disk from the image automatically. First boot takes about a minute after the VM is reachable. During that time it generates this VM's certificate, signing key and groot password, and it loads the demo graph.

2

Deploy from the Azure CLI

Run these commands on your own workstation:

RG="dgraph-prod"; LOCATION="eastus"; VM_NAME="dgraph-01" IMAGE_URN="sac1647283583153:dgraph-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create -g "$RG" --name dgraph-vnet --address-prefix 10.90.0.0/16 --subnet-name dgraph-subnet --subnet-prefix 10.90.1.0/24 az network nsg create -g "$RG" --name dgraph-nsg az network nsg rule create -g "$RG" --nsg-name dgraph-nsg --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp az network nsg rule create -g "$RG" --nsg-name dgraph-nsg --name allow-https --priority 110 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 443 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn "$IMAGE_URN"
az vm create -g "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
  --size Standard_B2s \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name dgraph-vnet --subnet dgraph-subnet --nsg dgraph-nsg --public-ip-sku Standard
3

Connect via SSH

ssh azureuser@<vm-ip>

The login banner shows the API and GraphQL URLs for this VM.

4

Check the services and health

Confirm that Zero, Alpha and nginx are running and that first boot has finished. Confirm that the health endpoint answers over HTTPS and that the installed version is 25.5.1:

systemctl is-active dgraph-zero.service dgraph-alpha.service nginx.service test -f /var/lib/sac/data/dgraph-firstboot.done && echo first-boot-done curl -sk -o /dev/null -w 'healthz: HTTP %{http_code}\n' https://localhost/healthz /opt/dgraph/bin/dgraph version 2>/dev/null | grep -E 'Dgraph version|Go version'

All three services report active, and the first-boot marker is present. /healthz returns HTTP 200, and the binary reports v25.5.1 built with go1.27.3.

Every Dgraph port is on loopback, and nginx owns the public ports:

sudo ss -tlnp | grep -E ':(5080|6080|7080|8080|9080|80|443) '

The output shows dgraph on 127.0.0.1 ports 5080 and 6080 (Zero) and 7080, 8080 and 9080 (Alpha). It shows nginx on ports 80 and 443.

5

Read the per-VM credentials

The groot password is unique to this VM. First boot writes it to a root-only file. Print only the field names, never the password itself:

sudo stat -c '%a %U:%G %n' /root/dgraph-credentials.txt
sudo grep -oE '^[A-Z_]+=' /root/dgraph-credentials.txt

The file is mode 600 and owned by root. It holds DGRAPH_HOST, DGRAPH_URL, DGRAPH_GRAPHQL_URL, DGRAPH_ADMIN_URL, DGRAPH_GROOT_USER (groot) and DGRAPH_GROOT_PASSWORD.

Read the password with sudo sed -n 's/^DGRAPH_GROOT_PASSWORD=//p' /root/dgraph-credentials.txt when you need it. Keep it safe, because groot is the Dgraph administrator (a member of the guardians group). Dgraph stores only a bcrypt hash of the password.

6

The authentication gate

Dgraph answers a refused request with HTTP 200 and an errors body, so read the message. A query without a token is refused. A wrong password and Dgraph's documented default password are refused too:

curl -sk -H 'Content-Type: application/dql' -X POST https://localhost/query \
  -d '{ q(func: has(name)) { uid } }' | grep -o 'requires an identified caller' printf '{"userid":"groot","password":"%s"}' 'wrong-password' | curl -sk -X POST --data-binary @- https://localhost/login | grep -o 'invalid username or password' printf '{"userid":"groot","password":"%s"}' 'password' | curl -sk -X POST --data-binary @- https://localhost/login | grep -o 'invalid username or password'

Each line prints the refusal. Now log in with the per-VM password to get an access token. The password is sent on standard input, so it never appears in the process list or your shell history:

PW=$(sudo sed -n 's/^DGRAPH_GROOT_PASSWORD=//p' /root/dgraph-credentials.txt)
JWT=$(printf '{"userid":"groot","password":"%s"}' "$PW" | curl -sk -X POST --data-binary @- https://localhost/login \
  | sed -n 's/.*"accessJWT":"\([^"]*\)".*/\1/p')
echo "access token received: ${#JWT} characters"

Send the token as the X-Dgraph-AccessToken header on every call. Access tokens last 6 hours. The /login response also carries a refreshJWT, which you can exchange for a new access token.

7

Query the demo graph with DQL

PW=$(sudo sed -n 's/^DGRAPH_GROOT_PASSWORD=//p' /root/dgraph-credentials.txt)
JWT=$(printf '{"userid":"groot","password":"%s"}' "$PW" | curl -sk -X POST --data-binary @- https://localhost/login \
  | sed -n 's/.*"accessJWT":"\([^"]*\)".*/\1/p')
curl -sk -H "X-Dgraph-AccessToken: $JWT" -H 'Content-Type: application/dql' -X POST https://localhost/query \
  -d '{ people(func: type(Person), orderasc: name) { name age friend { name } } }' \
  | python3 -c 'import json,sys; print(json.dumps(json.load(sys.stdin)["data"], indent=1))'

The answer lists Alice (31), Bob (27) and Carol (35). Each entry includes the friend that its friend edge points to.

8

Write your own data

Add to the schema, write nodes with an RDF mutation, and query them back. /alter changes the schema, and /mutate?commitNow=true writes and commits in one call:

PW=$(sudo sed -n 's/^DGRAPH_GROOT_PASSWORD=//p' /root/dgraph-credentials.txt)
JWT=$(printf '{"userid":"groot","password":"%s"}' "$PW" | curl -sk -X POST --data-binary @- https://localhost/login \
  | sed -n 's/.*"accessJWT":"\([^"]*\)".*/\1/p')
curl -sk -H "X-Dgraph-AccessToken: $JWT" -X POST https://localhost/alter \
  -d 'title: string @index(term) . directed_by: [uid] .'; echo
curl -sk -H "X-Dgraph-AccessToken: $JWT" -H 'Content-Type: application/rdf' -X POST 'https://localhost/mutate?commitNow=true' \
  -d '{ set { _:film <title> "The Graph Story" . _:film <directed_by> _:director . _:director <name> "Dana Director" . } }' | grep -o '"code":"Success"'
curl -sk -H "X-Dgraph-AccessToken: $JWT" -H 'Content-Type: application/dql' -X POST https://localhost/query \
  -d '{ films(func: anyofterms(title, "graph")) { title directed_by { name } } }' \
  | python3 -c 'import json,sys; print(json.dumps(json.load(sys.stdin)["data"], indent=1))'

The schema change returns "code":"Success", and the mutation commits. The query finds the film by a word in its title and follows the directed_by edge to its director.

9

GraphQL

Dgraph generates a complete GraphQL API from a GraphQL schema. Upload a schema to /admin/schema. Then query and mutate at /graphql with the same access token:

PW=$(sudo sed -n 's/^DGRAPH_GROOT_PASSWORD=//p' /root/dgraph-credentials.txt)
JWT=$(printf '{"userid":"groot","password":"%s"}' "$PW" | curl -sk -X POST --data-binary @- https://localhost/login \
  | sed -n 's/.*"accessJWT":"\([^"]*\)".*/\1/p')
curl -sk -H "X-Dgraph-AccessToken: $JWT" -X POST https://localhost/admin/schema \
  --data-binary 'type Task { id: ID! title: String! @search(by: [term]) done: Boolean }' | grep -o '"code":"Success"' sleep 3
curl -sk -H "X-Dgraph-AccessToken: $JWT" -H 'Content-Type: application/json' -X POST https://localhost/graphql \
  -d '{"query":"mutation { addTask(input: [{title: \"Try Dgraph GraphQL\", done: false}]) { task { title done } } }"}'; echo
curl -sk -H "X-Dgraph-AccessToken: $JWT" -H 'Content-Type: application/json' -X POST https://localhost/graphql \
  -d '{"query":"{ queryTask { title done } }"}'; echo

The schema upload returns "code":"Success". The mutation and the query both return the Try Dgraph GraphQL task. GraphQL subscriptions work over WebSocket at the same /graphql path for same-origin clients.

10

Query from your workstation

From your own machine, call https://<vm-ip>/. Open TCP 443 to your address in the network security group first.

The certificate is self-signed and unique to this VM. Either pass -k, or copy /etc/ssl/dgraph/dgraph.crt to your workstation and trust it with --cacert. Log in first, then send the token:

read -rs -p 'groot password: ' PW; echo
JWT=$(printf '{"userid":"groot","password":"%s"}' "$PW" | curl -sk -X POST --data-binary @- https://<vm-ip>/login \
  | sed -n 's/.*"accessJWT":"\([^"]*\)".*/\1/p')
curl -sk -H "X-Dgraph-AccessToken: $JWT" -H 'Content-Type: application/dql' -X POST https://<vm-ip>/query \
  -d '{ people(func: type(Person)) { name } }'

The gRPC port (9080) used by the official clients (dgo, pydgraph and dgraph-js) stays on loopback. Reach it, and Alpha's HTTP port, through an SSH tunnel from your workstation:

ssh -N -L 9080:127.0.0.1:9080 -L 8080:127.0.0.1:8080 azureuser@<vm-ip>

Point a client at localhost:9080 and log in as groot with the per-VM password. ACL applies on gRPC too.

Dgraph's Ratel query UI is a separate project and is not installed. If you run Ratel on your workstation, connect it to http://localhost:8080 through the same tunnel.

11

Where things live

These are the locations of the main components. Each line gives the component, then its path or command.

Dgraph binary (read-only, root-owned): /opt/dgraph/bin/dgraph

Units: dgraph-zero.service and dgraph-alpha.service, in /etc/systemd/system/

ACL signing key (per VM, mode 0640, owned by root:dgraph): /etc/dgraph/acl-secret

Data disk: /var/lib/dgraph

Zero write-ahead log: /var/lib/dgraph/zw

Alpha postings, write-ahead log and temporary files: /var/lib/dgraph/p, /var/lib/dgraph/w and /var/lib/dgraph/t

Exports: /var/lib/dgraph/export

Logs: journalctl -u dgraph-alpha -u dgraph-zero

Credentials file (mode 0600): /root/dgraph-credentials.txt

Per-VM certificate: /etc/ssl/dgraph/

nginx site: /etc/nginx/sites-available/sac-dgraph

License: /usr/share/doc/dgraph/LICENSE.txt

The data disk is mounted by filesystem UUID:

findmnt /var/lib/dgraph df -h /var/lib/dgraph

To change a Dgraph flag (for example Alpha's cache size), run sudo systemctl edit dgraph-alpha.service. Override ExecStart= and restart the service.

Keep --bindall=false, --acl "secret-file=/etc/dgraph/acl-secret" and --security "whitelist=127.0.0.1; anonymous=none" in place. They keep Dgraph on loopback with ACL enforced.

12

Exports and backups

Export the whole database as RDF (or json) into /var/lib/dgraph/export:

PW=$(sudo sed -n 's/^DGRAPH_GROOT_PASSWORD=//p' /root/dgraph-credentials.txt)
JWT=$(printf '{"userid":"groot","password":"%s"}' "$PW" | curl -sk -X POST --data-binary @- https://localhost/login \
  | sed -n 's/.*"accessJWT":"\([^"]*\)".*/\1/p')
curl -sk -H "X-Dgraph-AccessToken: $JWT" -H 'Content-Type: application/json' -X POST https://localhost/admin \
  -d '{"query":"mutation { export(input: {format: \"rdf\"}) { response { code message } } }"}'; echo
sudo ls /var/lib/dgraph/export

The call returns "code":"Success", and a dgraph.r... directory appears with the RDF and schema files. Copy exports off the VM, for example to Azure Blob Storage.

Take Azure snapshots of the /var/lib/dgraph data disk for point-in-time backups. Dgraph's backup mutation (full and incremental binary backups) also writes only under /var/lib/dgraph on this image, or to an S3-compatible or MinIO destination that you configure.

13

Create an application user

Use a dedicated user with only the permissions it needs instead of groot. Create a user and a group, and give the group read access to the name predicate. Replace the password with a strong one of your own:

PW=$(sudo sed -n 's/^DGRAPH_GROOT_PASSWORD=//p' /root/dgraph-credentials.txt)
JWT=$(printf '{"userid":"groot","password":"%s"}' "$PW" | curl -sk -X POST --data-binary @- https://localhost/login \
  | sed -n 's/.*"accessJWT":"\([^"]*\)".*/\1/p')
curl -sk -H "X-Dgraph-AccessToken: $JWT" -H 'Content-Type: application/json' -X POST https://localhost/admin \
  -d '{"query":"mutation { addGroup(input: [{name: \"readers\", rules: [{predicate: \"name\", permission: 4}]}]) { group { name } } }"}'; echo
curl -sk -H "X-Dgraph-AccessToken: $JWT" -H 'Content-Type: application/json' -X POST https://localhost/admin \
  -d '{"query":"mutation { addUser(input: [{name: \"app\", password: \"your-strong-password\", groups: [{name: \"readers\"}]}]) { user { name } } }"}'; echo

Permission 4 is read, 2 is write and 1 is modify (schema). Add the values together for combined rights.

14

Change the groot password

Rotate the administrator password whenever you need to. The command generates a new random password and updates the credentials file. It sends the password to Dgraph in a request body written to a root-only file in memory, never on a command line:

PW=$(sudo sed -n 's/^DGRAPH_GROOT_PASSWORD=//p' /root/dgraph-credentials.txt)
JWT=$(printf '{"userid":"groot","password":"%s"}' "$PW" | curl -sk -X POST --data-binary @- https://localhost/login \
  | sed -n 's/.*"accessJWT":"\([^"]*\)".*/\1/p')
NEW=$(head -c 600 /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 32)
REQ=$(mktemp -p /dev/shm); chmod 600 "$REQ" printf '{"query":"mutation { updateUser(input: {filter: {name: {eq: \\"groot\\"}}, set: {password: \\"%s\\"}}) { user { name } } }"}' "$NEW" > "$REQ" curl -sk -H "X-Dgraph-AccessToken: $JWT" -H 'Content-Type: application/json' -X POST --data-binary @"$REQ" https://localhost/admin; echo shred -u "$REQ" printf 'DGRAPH_GROOT_PASSWORD=%s\n' "$NEW" | sudo sh -c 'C=/root/dgraph-credentials.txt; { grep -v "^DGRAPH_GROOT_PASSWORD=" "$C"; cat; } > "$C.new" && chmod 600 "$C.new" && mv -f "$C.new" "$C"' printf '{"userid":"groot","password":"%s"}' "$PW" | curl -sk -X POST --data-binary @- https://localhost/login | grep -o 'invalid username or password' unset PW NEW JWT

Dgraph answers {"data":{"updateUser":{"user":[{"name":"groot"}]}}}. The credentials file now holds the new password, and the old password is refused. Access tokens issued before the change stay valid until they expire.

15

Serve under your own DNS name

Point a DNS record at the VM, for example an Azure DNS A record or the VM's public IP DNS label. Then regenerate the certificate and pin the served hostname:

sudo dgraph-set-hostname dgraph.example.com

This issues a new self-signed certificate for that name and makes nginx answer only for it. For production, replace it with your own CA-issued certificate.

For a public, browser-trusted certificate, you can also terminate TLS at Azure Application Gateway or Azure Front Door in front of the VM. Then open port 443 only to that front end.

Security notes

Dgraph 25.5.1 is the latest release at the time of writing. It includes the fix for the unauthenticated Zero admin endpoint advisory (GHSA-qj98-gp63-54xj) and every earlier Dgraph security fix. On top of that, this image hardens the deployment in these ways:

  • It switches on Dgraph ACL with a per-VM signing key and groot password, and it sets anonymous=none. Nothing except /login and /health works without a token.
  • It keeps every Dgraph port on loopback and publishes only the API paths through nginx. /debug returns 403, and Zero's admin endpoints (/state, /removeNode, /assign and /moveTablet) are never exposed.
  • It runs Zero and Alpha as an unprivileged user in a systemd sandbox. They can write only /var/lib/dgraph. They cannot see /root, /etc/shadow, /etc/ssh, the Azure agent's state, the credentials file or the TLS key.
  • It blocks the Azure Instance Metadata Service (169.254.169.254) and the Azure wire server (168.63.129.16) for Zero, Alpha, nginx and the first-boot service. A URL in a GraphQL @custom resolver, a lambda URL or a backup destination can therefore never reach the VM's managed identity tokens or agent channel. The Azure VM agent and cloud-init keep their normal access.
  • It refuses cross-origin state-changing requests and WebSocket upgrades at nginx, and it logs the real client address.

The debug and Zero endpoints are not published, and the metadata block is part of each service's unit:

curl -sk -o /dev/null -w 'debug endpoint via nginx: HTTP %{http_code}\n' https://localhost/debug/vars
curl -sk -o /dev/null -w 'zero removeNode via nginx: HTTP %{http_code}\n' https://localhost/removeNode systemctl show -p IPAddressDeny dgraph-zero.service dgraph-alpha.service nginx.service

nginx answers HTTP 403 and HTTP 404. All three units deny 169.254.169.254 and 168.63.129.16, plus their IPv6 and IPv4-mapped forms.

The sandbox refuses an export into a system directory:

PW=$(sudo sed -n 's/^DGRAPH_GROOT_PASSWORD=//p' /root/dgraph-credentials.txt)
JWT=$(printf '{"userid":"groot","password":"%s"}' "$PW" | curl -sk -X POST --data-binary @- https://localhost/login \
  | sed -n 's/.*"accessJWT":"\([^"]*\)".*/\1/p')
curl -sk -H "X-Dgraph-AccessToken: $JWT" -H 'Content-Type: application/json' -X POST https://localhost/admin \
  -d '{"query":"mutation { export(input: {format: \"rdf\", destination: \"/root/stolen\"}) { response { code } } }"}' \
  | grep -o 'resolving export failed because task failed'
sudo test -e /root/stolen || echo 'nothing was written to /root'

Dgraph reports that the export task failed and nothing is written.

Keep the VM patched with sudo apt-get update && sudo apt-get upgrade. Unattended security upgrades are enabled. Keep the groot password safe, and export or snapshot /var/lib/dgraph regularly. Expose only ports 22 and 443, and only to the networks that need them.

Support

Every SecureAnyCloud deployment includes 24/7 support with a 24-hour response SLA. Contact sales@secureanycloud.com for help with deployment, configuration or scaling.

This is a repackaged open-source software product with additional charges for SecureAnyCloud support services. Dgraph 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 Dgraph 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