Skip to content

Purple8 Hyper Graph — Cloud Deployment Guide ​

This guide covers deploying P8G on every major cloud platform. It is opinionated on purpose — for each platform it tells you exactly where things will break, so you don't have to learn it the hard way.

See also: DOCKER_QUICKSTART.md for local first-run and docs/SYSTEM_REQUIREMENTS.md for RAM/CPU sizing.


The one thing every platform must get right ​

P8G has one non-negotiable infrastructure requirement: P8G_DATA_PATH must point to a persistent, durable volume.

P8G_DATA_PATH is where BrickCoreStorage writes everything — the graph, the WAL, auth data, the HNSW index snapshots. If it points to a container's ephemeral filesystem, all data is wiped on every redeploy or restart.

Every section below tells you exactly how to satisfy this requirement on each platform — and what will silently go wrong if you don't.


Platform comparison ​

PlatformPersistent volumeEnv var behaviourRAM sizingCostRecommended for
Fly.io✅ Reliable — Fly Volumes✅ Immediate (fly secrets set, no redeploy)✅ Exact (choose machine RAM)~$5–20/moProduction cloud
Hetzner VPS✅ Block storage or local SSD✅ systemd EnvironmentFile✅ Full control€4–16/moSelf-hosted / on-prem
DigitalOcean Droplet✅ Block storage volumes✅ Standard env vars✅ Full control$6–24/moSelf-hosted / on-prem
Render⚠️ Available but auto-deleted after 90 days on free tier✅ Instant env var changes⚠️ Shared CPU on free tier$7/mo (starter)Dev/staging
Railway⚠️ Available but requires explicit volume + mount⚠️ Env var changes don't always trigger redeploy⚠️ No RAM guarantee on shared plans$5/mo (hobby)Stateless services only
AWS ECS (Fargate)✅ EFS mount or EBS✅ Parameter Store / Secrets Manager✅ Exact$15–50/moEnterprise / existing AWS
Red Hat OpenShift✅ PVCs (CSI block/file)✅ Secrets / ConfigMaps✅ Exact (Dockerfile.rhel UBI9)Cluster costEnterprise OpenShift / RHEL
GCP Cloud Run⚠️ Cloud Run is stateless by design — requires Cloud Filestore NFS mount✅ Instant⚠️ Requires NFS setup$10–30/moNot recommended for P8G
Self-hosted Docker✅ Local disk / mounted volume — most durable✅ docker run -e / compose env✅ Full hardwareHardware cost onlyCustomer on-prem

Fly.io is the best fit for P8G in the cloud. Its Machines API gives you deterministic RAM sizing, fly secrets set takes effect without a redeploy, and Fly Volumes are persistent across deployments.

Setup ​

1. Install flyctl and create your app:

bash
fly launch --no-deploy --name purple8-graph

2. Create a persistent volume (1 GB is enough to start — Fly can resize online):

bash
fly volumes create p8g_data --region lhr --size 1

3. Configure fly.toml:

toml
app = "purple8-graph"
primary_region = "lhr"

[build]

[mounts]
  source      = "p8g_data"
  destination = "/data"

[env]
  P8G_DATA_PATH = "/data"
  PORT          = "8080"

[[services]]
  internal_port = 8080
  protocol      = "tcp"

  [[services.ports]]
    port     = 443
    handlers = ["tls", "http"]

  [[services.ports]]
    port     = 80
    handlers = ["http"]

  [services.concurrency]
    type       = "connections"
    hard_limit = 25

[vm]
  memory      = "2gb"
  cpu_kind    = "shared"
  cpus        = 2

4. Set secrets (takes effect immediately — no redeploy needed):

bash
fly secrets set \
  JWT_SECRET=$(python3 -c "import secrets; print(secrets.token_hex(32))") \
  BOOTSTRAP_TOKEN=$(python3 -c "import secrets; print(secrets.token_hex(32))")

5. Deploy:

bash
fly deploy

6. Confirm the volume is mounted:

bash
fly ssh console -C "ls /data"

You should see auth/, storage/, wal/ directories appear after the first request.

What works well ​

  • fly secrets set KEY=value takes effect in ~5 seconds. No redeploy.
  • Fly Volumes survive restarts, deploys, and machine migrations.
  • fly scale memory 4096 resizes RAM live — P8G restarts in ~10 seconds.
  • fly logs gives you structlog JSON output in real time.
  • Rollback with fly deploy --image <previous-image> restores previous state from the same volume.

What to watch ​

  • A single Fly Volume is attached to a single region. For HA, use Fly's volume replication (fly volumes fork).
  • fly.toml [mounts] is not fly.toml [volumes] — use [mounts].
  • Free tier Fly machines are suspended after inactivity. Use min_machines_running = 1 in [http_service] if you need always-on.

Hetzner VPS — Best value for self-hosted ​

A Hetzner CX22 (2 vCPU, 4 GB RAM, €4/mo) runs P8G comfortably for corpora up to ~500k nodes. A CX32 (4 vCPU, 8 GB RAM, €8/mo) handles multi-million node graphs.

Setup ​

1. Create a server (Ubuntu 24.04 LTS): From the Hetzner Cloud Console or:

bash
hcloud server create --name p8g --type cx22 --image ubuntu-24.04 --location nbg1

2. Attach a block volume for data (optional but recommended for larger corpora):

bash
hcloud volume create --name p8g-data --size 20 --server p8g

3. Mount the volume:

bash
# On the server:
mkfs.ext4 /dev/disk/by-id/scsi-0HC_Volume_<id>
mkdir -p /data
echo '/dev/disk/by-id/scsi-0HC_Volume_<id> /data ext4 defaults,nofail 0 2' >> /etc/fstab
mount -a

4. Install Docker:

bash
apt-get update && apt-get install -y docker.io
systemctl enable --now docker

5. Create /etc/purple8/env:

bash
mkdir -p /etc/purple8
cat > /etc/purple8/env <<EOF
P8G_DATA_PATH=/data
JWT_SECRET=$(python3 -c "import secrets; print(secrets.token_hex(32))")
BOOTSTRAP_TOKEN=$(python3 -c "import secrets; print(secrets.token_hex(32))")
PORT=8080
EOF
chmod 600 /etc/purple8/env

6. Create a systemd service:

ini
# /etc/systemd/system/purple8-graph.service
[Unit]
Description=Purple8 Hyper Graph
After=network.target docker.service
Requires=docker.service

[Service]
EnvironmentFile=/etc/purple8/env
ExecStartPre=-/usr/bin/docker stop p8g
ExecStartPre=-/usr/bin/docker rm p8g
ExecStart=/usr/bin/docker run --rm \
  --name p8g \
  -p 8080:8080 \
  -v /data:/data \
  --env-file /etc/purple8/env \
  ghcr.io/purple8-technologies/purple8-graph:latest
ExecStop=/usr/bin/docker stop p8g
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
bash
systemctl daemon-reload
systemctl enable --now purple8-graph

7. Update env vars without rebuilding:

bash
# Edit the file, then restart:
nano /etc/purple8/env
systemctl restart purple8-graph

What works well ​

  • Cheapest option for persistent workloads.
  • Full RAM and CPU control — no noisy neighbours.
  • Block volumes can be resized online via hcloud volume resize.
  • systemd restart policy handles P8G crashes correctly.
  • journalctl -u purple8-graph -f gives you live structlog JSON output.

What to watch ​

  • No built-in TLS. Use caddy or nginx in front of P8G (apt install caddy).
  • Hetzner's public network is unfiltered — add ufw allow 22 && ufw allow 443 && ufw enable immediately.
  • Block volumes are tied to one datacenter location. Snapshots are your backup strategy.

Railway — Proceed with caution for stateful services ​

Railway is excellent for stateless FastAPI services (like Purple8 Command Center). It is not ideal for P8G because of two compounding problems:

Problem 1: Ephemeral filesystem by default ​

Railway containers use an ephemeral filesystem. Without a Volume explicitly attached, P8G_DATA_PATH=./data writes to the container FS — wiped on every redeploy, every restart, every crash.

Fix: Railway → your P8G service → Volumes → add a volume → mount path /data → set P8G_DATA_PATH=/data in Variables.

Problem 2: Variable changes don't always trigger a redeploy ​

Railway sometimes requires a manual redeploy after adding or changing env vars. The new value isn't loaded until the container restarts.

Fix: After any variable change, go to Deployments tab → click Redeploy on the latest deployment.

Problem 3: No RAM guarantee on shared plans ​

Railway's Hobby plan allocates shared CPU and RAM. P8G's CortexAutoConfig probes available RAM at startup to size the processing envelope. On a shared container, psutil may report RAM from the host — leading to over-allocation that Railway then OOM-kills.

Fix: Either use Railway's Pro plan (dedicated RAM) or set HNSW_M and BATCH_SIZE explicitly to stay under 512 MB:

bash
# Conservative settings for Railway shared plan
HNSW_M=8
HNSW_EF_CONSTRUCTION=64
BATCH_SIZE=500

What works well on Railway ​

  • Stateless services with no P8G_DATA_PATH dependency (Command Center, DocIntel).
  • Fast prototypes where data loss on redeploy is acceptable.
  • Services that read from P8G but don't write (query-only replicas).
toml
# railway.toml
[build]
builder = "DOCKERFILE"

[deploy]
startCommand = "sh -c 'PYTHONPATH=src uvicorn purple8_graph.secure_server:app --host 0.0.0.0 --port ${PORT:-8080} --workers 1'"
healthcheckPath = "/health"
healthcheckTimeout = 30
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 3

Required Railway Variables:

P8G_DATA_PATH    = /data          ← must match your Volume mount path
JWT_SECRET       = <token_hex_32>
BOOTSTRAP_TOKEN  = <token_hex_32>
WORKERS          = 1              ← multi-worker needs P8G_DATA_PATH on shared storage

Render — Good for staging ​

Render's managed disks are reliable and env var changes apply on the next deploy (triggered automatically). The free tier uses shared CPU with sleeping instances — use the Starter plan ($7/mo) for always-on.

Setup ​

1. Create a Web Service from your Docker image or repo.

2. Add a Disk: Render Dashboard → your service → Disks → add disk → mount path /data → size 1 GB.

3. Add env vars:

P8G_DATA_PATH    /data
JWT_SECRET       <token_hex_32>
BOOTSTRAP_TOKEN  <token_hex_32>

4. Important: Render free-tier disks are deleted after 90 days of inactivity. Use the paid tier for any persistent data.

What works well ​

  • Env var changes trigger an automatic redeploy.
  • Disks are persistent across restarts and deploys on paid plans.
  • Auto-TLS with custom domain in one click.

What to watch ​

  • Render's free plan CPU-throttles sleeping containers — cold start after inactivity can take 30–60 seconds.
  • Disk IOPS on Starter is lower than Fly.io or Hetzner. For write-heavy ingestion, use a larger paid tier.

AWS ECS (Fargate) — Enterprise / existing AWS ​

For teams already in AWS, Fargate + EFS (Elastic File System) is the correct architecture. EBS is faster but cannot be shared across tasks.

Minimal task definition (excerpt) ​

json
{
  "containerDefinitions": [{
    "name": "purple8-graph",
    "image": "ghcr.io/purple8-technologies/purple8-graph:latest",
    "environment": [
      {"name": "P8G_DATA_PATH", "value": "/data"},
      {"name": "PORT",          "value": "8080"}
    ],
    "secrets": [
      {"name": "JWT_SECRET",       "valueFrom": "arn:aws:ssm:..."},
      {"name": "BOOTSTRAP_TOKEN",  "valueFrom": "arn:aws:ssm:..."}
    ],
    "mountPoints": [{
      "sourceVolume": "p8g-data",
      "containerPath": "/data",
      "readOnly": false
    }]
  }],
  "volumes": [{
    "name": "p8g-data",
    "efsVolumeConfiguration": {
      "fileSystemId": "fs-xxxxxxxx",
      "transitEncryptionEnabled": true
    }
  }]
}

Secrets via Parameter Store:

bash
aws ssm put-parameter \
  --name /p8g/JWT_SECRET \
  --value "$(python3 -c "import secrets; print(secrets.token_hex(32))")" \
  --type SecureString

What works well ​

  • EFS survives task replacement, scaling, and AZ failover.
  • IAM task roles eliminate long-lived credentials from the container.
  • CloudWatch Logs integrates with P8G's structlog JSON output natively.
  • ECS Service Connect handles service discovery if running multiple services.

What to watch ​

  • EFS IOPS are ~3 IOPS/GB provisioned. For write-heavy workloads, provision throughput explicitly or use bursting mode.
  • Fargate has no persistent local disk — everything must go to EFS.
  • Cold start on Fargate is ~10–30 seconds. Use Application Auto Scaling with a minimum of 1 task.

Cloud Run is a stateless platform by design. Every instance starts with a clean filesystem. Persistent storage requires mounting a Cloud Filestore NFS volume — which adds ~10 seconds to cold start, costs extra, and introduces a network FS failure mode that affects P8G's WAL.

Recommendation: Use GKE Autopilot with a PersistentVolumeClaim instead. Cloud Run is the wrong substrate for a stateful engine.

If you are committed to Cloud Run:

  1. Provision a Cloud Filestore instance (Basic HDD, 1 TB minimum — smaller tiers are not available)
  2. Mount via VPC connector
  3. Set P8G_DATA_PATH=/mnt/p8g-data
  4. Accept the cold-start and IOPS overhead

Self-hosted Docker & Red Hat OpenShift — The right answer for most customers ​

The commercial Docker image (Dockerfile.commercial) and the Red Hat OpenShift-certified UBI9 image (Dockerfile.rhel, based on registry.access.redhat.com/ubi9/python-312:9.5) are the recommended deployment paths for any customer who:

  • Wants to run Purple8 Hyper Graph (v0.72.0) on their own hardware or Kubernetes/OpenShift cluster
  • Cannot (or will not) manage a public cloud service
  • Runs air-gapped or on a regulated network

Purple8 is an approved Red Hat Technology Partner and NVIDIA Inception program member. Dockerfile.rhel is engineered specifically for Red Hat OpenShift's default restricted Security Context Constraint (arbitrary-UID assignment, group 0 write permissions across /data, /backups, /app/logs, /opt/venv, and /app, /licenses/LICENSE, and non-root UID 1001 default).

A single docker run (or oc new-app on OpenShift) with a mounted data volume brings up the engine, REST API, 125 MCP tools across 16 namespaces, and LCNC admin UI:

DeploymentDefault P8G_DATA_PATH
Docker / Podman / OpenShift (volume mount)/data inside the container → host path or PVC of your choice
pip wheel (pip install purple8-hyper-graph)~/.local/share/purple8/graph/data

For Docker, set -v /host/path:/data and P8G_DATA_PATH=/data. No further configuration needed.


Environment variable cheatsheet ​

These variables must always be set in production, regardless of platform:

VariableRequiredDescriptionGenerate with
P8G_DATA_PATHCriticalPersistent storage root. Must point to a mounted volume.Set to /data (or your mount path)
JWT_SECRETCriticalSigns all auth tokens. Changing this invalidates all active sessions.python3 -c "import secrets; print(secrets.token_hex(32))"
BOOTSTRAP_TOKENRequired onceGates first-owner creation at /auth/setup. Rotate after first use.python3 -c "import secrets; print(secrets.token_hex(32))"
PORTPlatform-setInjected by Fly/Railway/Render automatically. Default 8080.—
WORKERSOptionalNumber of uvicorn workers. Keep at 1 unless your storage is on shared FS.1

Variables you should not need to set (P8G handles them via CortexAutoConfig):

VariableWhy you don't need it
HNSW_M, HNSW_EF_CONSTRUCTIONAuto-sized to fit available RAM at startup
BATCH_SIZEAuto-sized by CortexAutoConfig based on available RAM
MAX_MEMORY_GBNot a P8G variable — RSS is bounded by the Cortex model, not a config cap

Diagnosing P8G_DATA_PATH problems ​

If you see any of these symptoms after a redeploy, P8G_DATA_PATH is almost certainly pointing to an ephemeral path:

  • setup_required: true on /health after a previously completed setup
  • Users created yesterday are gone
  • API keys no longer work
  • The graph appears empty

Diagnosis:

bash
# Check what P8G actually resolved at startup:
# Look for this line in your platform's log output:
# event="data_path_resolved" data_path="/data"          ← correct
# event="data_path_ephemeral" data_path="./data"        ← problem

Fix sequence:

  1. Attach a persistent volume and mount it at /data on your platform (see per-platform sections above)
  2. Set P8G_DATA_PATH=/data in your env vars
  3. Redeploy
  4. Confirm data_path_resolved data_path=/data in startup logs
  5. Re-run /auth/setup (or /portal/bootstrap for Command Center) — data was lost on the ephemeral path

Multi-worker deployments ​

P8G's AuthManager handles multiple workers correctly via mtime-based file reload (TTL=2s). However, WORKERS > 1 requires all workers to share the same P8G_DATA_PATH. This means:

SetupWorkersNote
Single container, local volume1 or 2Correct — all workers share the same mounted path
Multiple containers / replicas1 per containerDo not run replicas pointing at separate volumes — graph state will diverge
Multiple containers, shared NFS (EFS/Filestore)1 per containerSupported — mount the same NFS path on all replicas

For most deployments: WORKERS=1 is correct. P8G is I/O-bound on ingest and CPU-bound on vector search — horizontal scaling is done by adding more P8G instances with separate data paths (tenant isolation), not by adding workers to one instance.

Purple8 Hyper Graph v0.72.0 · AI-Native Execution Infrastructure · NVIDIA Inception Member · Red Hat Technology Partner.