
This blog is written by AI for SEO
Self-Hosted Vector Database With Graph Queries: HelixDB in Docker
Setting up a split memory stack for AI agents gets messy fast. You start with a standalone vector database for similarity search, then discover your agent drops relational context between documents, entities, and tool calls. So you spin up a graph database next to it. Within two weeks, your application code turns into brittle synchronization logic: two network round trips, two transactions that fail independently, and IDs duct-taped together in application memory.
HelixDB kills that split. It puts graph traversal, approximate nearest-neighbor vector search, and BM25 text retrieval in a single Rust engine. Vectors live directly as properties on nodes and edges instead of sitting in an external index. This guide covers how to set up a self hosted vector database with graph queries using the official CLI and Docker, configure your local storage mode, insert graph-vector records, and run a single query that pre-filters similarity search across graph relationships.
Step 1: Install the Helix CLI and check Docker is running
Running HelixDB locally requires Docker (or Podman) and the Helix CLI. The CLI handles image pulls, local daemon management, and configuration scaffolding, so you don't have to write raw compose files from scratch.
First, confirm Docker is running in your terminal:
docker info > /dev/null 2>&1 && echo "Docker is active" || echo "Docker is not running"If Docker is not running, start your local Docker Desktop or system daemon before proceeding. Next, install the Helix CLI using the official installation script, or via cargo if you work mostly in Rust:
curl -fsSL https://install.helix-db.com | bashVerify that the executable is on your shell path:
helix --versionThe CLI is the control plane for local instances and talks directly to your container runtime. Some developers prefer handwriting Docker Compose manifests, but the CLI wraps runtime checks, volume mapping, and environment variable validation into standard commands. If you run Podman instead of Docker, set the DOCKER_HOST environment variable to point to your Podman socket before initializing containers.
Step 2: Run helix init and helix start dev
Once the CLI is available, initialize your local workspace. Create a dedicated project directory and run the initialization command:
mkdir agent-memory && cd agent-memory
helix initThis command writes a local helix.toml configuration file to your directory. It defines the database listening port, default storage engine targets, and telemetry options. By default, local instances bind HTTP traffic to port 6969. Check the documentation on local development servers if you need to adjust port bindings or container memory limits.
Start the local development instance using the CLI:
helix start devThis command checks for the latest Apache-2.0 container image, maps port 8080, and starts the container in the background. Check that the server is responding to health checks:
curl http://localhost:8080/healthA healthy response returns a 200 status code immediately. You now have a working open source graph database written in rust handling requests locally.
Step 3: Choose a storage mode: in-memory, on-disk, or S3-compatible object storage
HelixDB uses SlateDB as its underlying storage engine. Some graph databases force the entire vector index into memory, and some vector databases ignore disk-backed adjacency lists. SlateDB lets you run the same binary across three persistence tiers.
You choose the storage mode via configuration flags in helix.toml or environment variables passed to the container:
In-memory: Fastest for automated test suites and ephemeral CI pipelines. When the container stops, data drops instantly. Set
storage.mode = "memory".On-disk: Standard for local development, workstations, and single-node production environments. Data persists across container restarts using a mounted volume. Set
storage.mode = "disk"and specify a directory path likestorage.path = "/data/helix".S3-compatible object storage: The engine writes log-structured data files directly to object storage targets while keeping an active local disk cache. Set
storage.mode = "object"alongside bucket credentials and endpoint configurations.
For a standard self-hosted developer setup, on-disk storage hits the right balance between zero configuration and persistence. Update helix.toml to mount a persistent directory:
[storage]
mode = "disk"
path = "/var/lib/helix/data"
[server]
host = "0.0.0.0"
port = 8080Restart the development container with helix restart dev to apply the storage configuration. The storage engine treats disk blocks and object storage blocks the same way. Because of that, vector indexes and graph adjacency lists stay tightly aligned, and you don't run out of RAM as your corpus grows.
Step 4: Load a few nodes, edges and embeddings
In HelixDB, vectors are not isolated records in a secondary table. They are first-class properties on nodes and edges.
Take an agent memory graph with team projects, documents, and concepts. You want to store documents linked to specific project nodes, with document chunks carrying embedding vectors.
You can talk to the server using the official TypeScript SDK (@helix-db/helix-db) or by sending raw JSON requests to POST /v2/query. Here is how to insert two documents and connect them to a project entity using TypeScript:
import { writeBatch } from "@helix-db/helix-db";
async function seedMemory() {
const mutation = writeBatch([
{
type: "create_node",
label: "Project",
properties: {
name: "Core Engine Refactor",
projectId: "proj_101"
}
},
{
type: "create_node",
label: "Document",
properties: {
docId: "doc_a",
title: "Memory Architecture Spec",
embedding: [0.042, -0.018, 0.089, 0.012] // 4D toy vector
}
},
{
type: "create_node",
label: "Document",
properties: {
docId: "doc_b",
title: "Marketing Plan",
embedding: [0.038, -0.012, 0.081, 0.009]
}
},
{
type: "create_edge",
label: "BELONGS_TO",
from: { label: "Document", key: "docId", value: "doc_a" },
to: { label: "Project", key: "projectId", value: "proj_101" },
properties: {
created_at: 1718000000
}
}
]);
const response = await fetch("http://localhost:8080/v2/query", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(mutation)
});
console.log(await response.json());
}
seedMemory();Writes commit atomically. The graph topology and the vector properties enter the storage engine in the same transaction. If an edge creation fails, the matching nodes don't leave orphaned vectors in an uncoordinated index.
Step 5: Run a vector search scoped to graph-related nodes (POST /v2/query or the TypeScript SDK)
Pure vector search breaks down when your agent asks a targeted question. Query an unrestricted vector database for "memory architecture spec" and the nearest neighbor search calculates cosine distance across your entire index. An old document from an archived project can outrank the relevant file just because its embedding sits closer in vector space.
A self hosted vector database with graph queries fixes this with vector pre-filtering. You traverse the graph to gather candidate nodes, then run vector similarity only over that filtered set. The engine doesn't waste compute scoring vectors outside the graph neighborhood.
Here is a raw JSON query sent to POST /v2/query that finds documents linked to the project proj_101 and ranks only those documents against an incoming query vector:
{
"steps": [
{
"op": "find_nodes",
"label": "Project",
"where": { "projectId": { "$eq": "proj_101" } }
},
{
"op": "in_edges",
"label": "BELONGS_TO"
},
{
"op": "source_nodes",
"label": "Document"
},
{
"op": "vector_search",
"property": "embedding",
"vector": [0.040, -0.015, 0.085, 0.010],
"metric": "cosine",
"limit": 5
}
]
}The engine parses this JSON payload, runs the traversal from the Project node to connected Document nodes, and limits the approximate nearest neighbor search to those pre-filtered candidates. Look at what disappears: you don't fetch node IDs in code, format an SQL IN (...) clause, and push it to a secondary vector database. For more complex edge conditions, you can also scope queries using vector search on graph edges.
Keep the documented limits in mind. An unrestricted vector search caps at 800 effective results. If a traversal-scoped search candidate stream exceeds 1,000,000 unique entities, the query returns an error instead of degrading memory stability.
Step 6: Know what self-hosting covers and what is Cloud-only
Self-hosting HelixDB gives you the full core engine under the Apache-2.0 license. The local binary includes the graph traversal pipeline, BM25 full-text indexing, vector pre-filtering, and ACID transaction commits across memory, disk, and object storage.
Self-hosting does not include the automated operational architecture built into HelixDB Cloud. If you manage the database yourself on bare metal or cloud virtual machines, you own these pieces:
High availability and failover: The open-source build runs as a single server process. Cloud deploys a multi-node cluster with a dedicated gateway router, a single writer node that serializes mutations, and horizontally scaled reader nodes.
Autoscaling readers: In HelixDB Cloud, read replicas spin up dynamically based on traffic. When self-hosting, you scale manually.
Distributed cache invalidation: Cloud handles tiered memory caching across separate read nodes automatically.
Backup orchestration: Self-hosted deployments need your own snapshot schedules for SlateDB disk files or object storage buckets.
Teams building internal tools, local AI workflows, or cost-sensitive agent loops get everything they need from the self-hosted Docker container. When your traffic grows to millions of requests and horizontal read scaling and managed uptime matter, moving from self-hosted to Cloud means changing only your connection endpoint and credentials. The SDK calls and JSON query formats stay identical.
What to do next: troubleshooting, GitHub star, Discord
If your container fails to start, first check for port collisions on port 8080 by running lsof -i :8080. If another process holds the port, adjust the binding in helix.toml.
If you see permissions errors in on-disk storage mode, verify that your local Docker daemon has read and write permissions to the mounted directory. On Linux, check that SELinux or default folder ownership doesn't block container write access to the host path.
To check whether a combined architecture fits your production roadmap, compare the tradeoffs between dedicated systems in our breakdown of vector database vs graph database.
If you hit edge cases or want to request new language SDK bindings, jump into the official HelixDB Discord community and share your implementation. Star the open-source repository on GitHub (github.com/HelixDB/helix-db) to track releases, engine optimizations, and new indexing features straight from the core team.
Conclusion
Duct-taping a vector store to a graph database creates latency spikes and synchronization bugs that compound with every new agent feature. Running a self hosted vector database with graph queries inside Docker lets you store knowledge graphs, text indexes, and dense embeddings in one native Rust engine.
Take fifteen minutes today to pull the container, configure local disk storage, and run your first traversal-scoped vector query. Star the project on GitHub (github.com/HelixDB/helix-db) and start building agent memory that keeps relationships intact without adding infrastructure complexity.