Skip to main content
The Compose deploy is the right path for air-gapped, single-VM, and POC / evaluation deployments — anywhere standing up a Kubernetes cluster is overkill or impossible. The bundle is identical regardless of which path you choose: same application binaries, same database schema, same release-contract attestations. Migrating from Compose to EKS later does not require a data re-import.

Prereqs

  • Linux host with docker (24+) and docker compose (v2.30+, ideally v2.40+ for healthcheck and profile semantics)
  • ~50 GB free disk (graph-engine WAL + compactor scratch + Postgres + Queue)
  • 16 GB RAM minimum, 32 GB recommended for production
  • OpenAI, Anthropic, or Azure OpenAI credentials. For air-gapped deploys with no cloud LLM, use the compose.vllm.yaml overlay (below)
If you’re on AWS and want to back the durable state to RDS + S3 instead of running Postgres / MinIO in-stack, follow Managed AWS resources. enterprise/bootstrap.sh auto-detects which in-stack services to skip based on the *_HOST values in your .env.enterprise.

Install

For JWT verifier behavior and signing-key rotation, see Service authentication. The bootstrap script orders the infrastructure (Postgres, MinIO, Orchestration) → migrations → token derivation → application services. Catalog-bootstrap runs Alembic, creates the required Postgres extensions (vector, pg_partman, pg_cron), and applies the catalog contract. End-to-end first boot takes 2-4 minutes; subsequent re-runs are a no-op if everything is already healthy.

What runs in the stack

ServiceRoleExternalizable?
nebulaAPI + ingest pipeline
nebula-workerOrchestration worker pool
graph-engineRust graph + vector store
postgresNebula application databaseYes → RDS
minio + minio-initS3-compatible object storageYes → S3
See Managed AWS resources for the externalization knobs.

Air-gapped: local LLM with vLLM

For deployments with no internet egress, stack the compose.vllm.yaml overlay on top of compose.enterprise.yaml by passing --overlay to bootstrap.sh:
Running through bootstrap.sh (rather than calling docker compose -f compose.enterprise.yaml -f compose.vllm.yaml up -d directly) is required so the in-stack postgres and minio services activate via COMPOSE_PROFILES — those services are profile-gated and won’t start under a raw docker compose up. The overlay runs vLLM for completions and TEI for embeddings, then flips every Nebula service to NEBULA_CONFIG_NAME=onprem_local. Completion calls route to http://vllm-instruction:8000/v1; embedding calls route to http://tei-embedding:8000/v1. No cloud LLM API key is required.
The default nebula-enterprise-<version>.tar.gz bundle does not include the vLLM or TEI images — the instruction image is ~10 GB and most customers route to OpenAI, Anthropic, or Azure OpenAI instead. Two options to obtain them:
  1. Request an inference-inclusive bundle from support. We can produce nebula-enterprise-<version>-vllm.tar.gz (built with bundle.sh --include-vllm) and ship it via the encrypted-delivery path. Use this for truly air-gapped hosts with no public-registry egress.
  2. Side-load the inference images yourself. If you have a separate host that can reach public registries, pull and save the images there, transfer the tarball, and load on the air-gapped host:
The exact inference image tags the overlay expects are pinned in compose.vllm.yaml; check that file for the versions to pull.
GPU prereqs: NVIDIA Container Toolkit installed on the host; GPU capacity for the default Qwen3.5 completion model plus Qwen3 embedding model.

Upgrade

Single-host restart; ~30s downtime on the API tier while pods cycle. Catalog-bootstrap re-runs Alembic to apply any new migrations before the API is back in service.

Stopping the stack

The bundle namespaces containers, networks, and persistent Docker volumes with COMPOSE_PROJECT_NAME from env/.env.enterprise (default nebula_enterprise). Use a unique project name for separate deployments on the same host; keep it stable when upgrading a deployment that should reuse existing data. If an older bundle created unprefixed volumes such as postgres_data, bootstrap.sh fails before starting services. Remove those volumes for a fresh deployment, or manually copy them into the matching project-prefixed volumes only after verifying the source deployment and secrets belong to this upgrade:
postgres:16.6 is included in images.tar; if you are running from a source checkout instead of a bundle, pull it first. Include any other legacy volumes that bootstrap reports.

Troubleshooting

enterprise/generate-secrets.sh didn’t run, or env/.env.enterprise is missing the secrets section. Re-run ./enterprise/generate-secrets.sh ./env/.env.enterprise and try again. The script refuses to overwrite an existing file, so delete it first if you intend to regenerate (warning: this rotates every secret).
The bundled Postgres image carries vector, pg_partman, and pg_cron, so this only happens if you’ve pointed at an external Postgres missing required extensions. On RDS/Aurora, make all three extensions available to the master user; if your parameter group restricts extension installs with rds.allowed_extensions, include them there. Set shared_preload_libraries=pg_cron and cron.database_name to the Nebula database name before bootstrap.
Select a config that matches your provider credentials: full_openai with OPENAI_API_KEY, full_anthropic with ANTHROPIC_API_KEY, or full_azure with AZURE_API_KEY and AZURE_API_BASE.
The JWT signing key is missing from the deployment. Re-run ./enterprise/generate-secrets.sh ./env/.env.enterprise on a clean env directory, or restore the secrets/enterprise-jwt.pem file and NEBULA_JWT_KID value from your previous deployment. See Service authentication before rotating JWT keys.
For in-stack MinIO: check that AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in .env.enterprise match MINIO_ROOT_USER and MINIO_ROOT_PASSWORD. generate-secrets.sh populates all four to the same value; if you edited any of them manually, restore the match.