Deployment guide

Dgraph 25.5.1

Overview

This image runs Dgraph 25.5.1 on Ubuntu 24.04 on Amazon EC2. It is one version of the SecureAnyCloud Hardened Neo4j & Graph and RDF Databases AMIs listing on AWS Marketplace, which you pick as the software version when you subscribe and launch. Every product in the listing is described at /guides/graph-databases-aws/.

Dgraph is a distributed, horizontally scalable native graph database. It stores data as nodes and typed edges, answers deep graph traversals, full-text, regular-expression, geo and vector-indexed queries, and speaks two query languages: DQL (Dgraph Query Language) over HTTP and gRPC, and GraphQL, generated from a GraphQL schema you upload. One dgraph binary runs two services: Zero, which coordinates the cluster, and Alpha, which 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 and 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 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, so a caller who has not logged in may only call /login and /health. Every query, mutation, schema change, GraphQL request and admin call needs an access token.
  • HTTPS from the first boot. nginx serves the Dgraph HTTP and GraphQL APIs on port 443 with a certificate generated on the instance for its own hostname. Port 80 only redirects to HTTPS, and requests for any other hostname are dropped.
  • Every Dgraph port is loopback only. 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 port are never exposed.
  • Sandboxed services. Zero and Alpha run as an unprivileged dgraph user in a systemd sandbox that can write only /var/lib/dgraph, cannot read system secrets such as /etc/shadow, the credentials file or the TLS key, and cannot reach the instance metadata service. Exports and backups therefore can only be written under /var/lib/dgraph.
  • Fully patched Ubuntu 24.04 at build time, with per-instance secrets and the SecureAnyCloud security defaults.

What is on the image: Ubuntu 24.04 LTS, Dgraph 25.5.1 (the official release binary, checksum-verified) in /opt/dgraph, all Dgraph data on a dedicated 30 GiB data volume at /var/lib/dgraph, nginx, and the SecureAnyCloud first-boot service. First boot also loads a tiny demo graph (three Person nodes joined by friend edges) so your first query returns data.

Connecting to your instance

Connect over SSH as the ubuntu user (Step 2). The API is served at https://<public-dns>/.

Prerequisites

  • An AWS account and permission to launch EC2 instances.
  • An EC2 key pair in the Region you launch in.
  • A security group that allows TCP 22 from your own address, and TCP 443 (and optionally 80, which only redirects) from the clients that will use the API. The Dgraph ports 5080, 6080, 7080, 8080 and 9080 are never exposed, because they listen on loopback only.

Recommended instance type: m5.large (2 vCPU, 8 GiB). Dgraph Alpha keeps hot data in memory, so choose a larger instance for bigger graphs.

1

Launch the instance

Option A: AWS Marketplace console

  • Open the SecureAnyCloud Hardened Neo4j & Graph and RDF Databases AMIs listing on AWS Marketplace, choose View purchase options, then Subscribe. One subscription covers every version in the listing.
  • Choose Launch your software. In Version, choose Dgraph 25.5.1 on Ubuntu 24.04 - YYYY-MM-DD, and select your Region.
  • Instance type: choose m5.large, which is recommended.
  • Key pair: choose your EC2 key pair.
  • Security group: allow TCP 22 from your own address, and TCP 443 (and 80) from your clients.
  • Launch the instance. First boot takes under a minute after the instance is reachable. It generates this instance's certificate, signing key and groot password, and loads the demo graph.

Option B: AWS CLI

Run this on your own workstation. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and replace the key pair, security group and subnet with your own:

aws ec2 run-instances \
  --image-id ami-0123456789abcdef0 \
  --instance-type m5.large \
  --key-name my-key-pair \
  --security-group-ids sg-0123456789abcdef0 \
  --subnet-id subnet-0123456789abcdef0 \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=dgraph}]'

The AMI already requires IMDSv2, so you do not need --metadata-options. The data volume is created from the AMI automatically.

2

Connect via SSH

Connect with your key pair:

ssh -i my-key-pair.pem ubuntu@<vm-ip>

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

3

Check the services and health

Confirm that Zero, Alpha and nginx are running, first boot has finished, the health endpoint answers over HTTPS, and 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, 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. Check the listeners:

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

You see dgraph on 127.0.0.1 ports 5080 and 6080 (Zero) and 7080, 8080 and 9080 (Alpha), and nginx on ports 80 and 443.

4

Read the per-instance credentials

The groot password is unique to this instance and is written to a root-only file at first boot. Print only the field names, not the password itself:

sudo grep -oE '^[A-Z_]+=' /root/dgraph-credentials.txt

This lists 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, and keep it safe. groot is the Dgraph administrator, a member of the guardians group. Dgraph stores only a bcrypt hash of the password.

5

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, and so are a wrong password and Dgraph's documented default password:

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. Next, log in with the per-instance 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 that you can exchange for a new access token.

6

Query the demo graph with DQL

Log in, then query the three demo Person nodes:

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 with the friend their friend edge points to.

7

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.

8

GraphQL

Dgraph generates a complete GraphQL API from a GraphQL schema. Upload one 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", and 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.

9

Where things live

These are the main locations on the instance:

  • 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 instance, mode 0640 root:dgraph): /etc/dgraph/acl-secret
  • Data volume: /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: /root/dgraph-credentials.txt (mode 0600)
  • Per-instance certificate: /etc/ssl/dgraph/
  • nginx site: /etc/nginx/sites-available/sac-dgraph
  • Licence: /usr/share/doc/dgraph/LICENSE.txt

The data volume is mounted by filesystem UUID. Check it:

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" so Dgraph stays on loopback with ACL enforced.

10

gRPC clients and Ratel

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

ssh -i my-key-pair.pem -N -L 9080:127.0.0.1:9080 -L 8080:127.0.0.1:8080 ubuntu@<vm-ip>

Point a client at localhost:9080 and log in as groot with the per-instance 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

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 them up for combined rights. Change groot's own password the same way with updateUser(input: {filter: {name: {eq: "groot"}}, set: {password: "..."}}), then update /root/dgraph-credentials.txt.

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 instance (for example to Amazon S3), and snapshot the /var/lib/dgraph EBS volume 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 or MinIO destination you configure.

13

Serve under your own DNS name

Point a DNS record at the instance, 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- or ACM-issued certificate. For a public, browser-trusted certificate, terminate TLS at an Application Load Balancer or CloudFront in front of the instance, and keep the security group's 443 open only to that front.

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:

  • Switches on Dgraph ACL with a per-instance signing key and groot password, and anonymous=none, so nothing but /login and /health works without a token.
  • Keeps every Dgraph port on loopback and publishes only the API paths through nginx. /debug returns 403 and Zero's admin API is never exposed.
  • Runs Zero and Alpha as an unprivileged user in a systemd sandbox. They can write only /var/lib/dgraph, and /root, /etc/shadow, /etc/ssh, the credentials file and the TLS key are not visible to them.
  • Blocks the instance metadata service (169.254.169.254) for Zero, Alpha and nginx, so a URL in a GraphQL @custom resolver, a lambda URL or a backup destination can never reach the instance's credentials.
  • Refuses cross-origin state-changing requests and WebSocket upgrades at nginx, and logs the real client address.

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

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

nginx answers HTTP 403, and all three units deny 169.254.169.254 and 168.63.129.16 (plus the IPv6 metadata address).

The sandbox refuses an export into a system directory. Try one:

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 instance patched (sudo apt-get update && sudo apt-get upgrade), keep the groot password safe, export or snapshot /var/lib/dgraph regularly, and expose only ports 22 and 443 to the networks that need them.

Support

SecureAnyCloud provides 24/7 technical support for every version in this listing by email and live chat, 365 days a year, with a 24-hour response SLA: sales@secureanycloud.com.

Trademarks

Dgraph is a trademark of its respective owner. SecureAnyCloud is not affiliated with or endorsed by the Dgraph project or its maintainers.

Deploy on AWS

Launch Dgraph 25.5.1 on Ubuntu 24.04 with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS