Fluree knowledge graph db. SPARQL, Cypher, policy, reasoning, time travel, raw speed.
10K+
For full documentation, visit labs.flur.ee.
The official image (fluree/server) ships the fluree binary on a slim Debian base. This guide covers what's inside the image, how to configure it (env vars, mounted config files, CLI flags), and worked recipes for the common production patterns.
| Aspect | Value |
|---|---|
| Base | debian:trixie-slim |
| Entrypoint | /usr/local/bin/fluree-entrypoint.sh |
| Default command | fluree server run |
WORKDIR | /var/lib/fluree |
VOLUME | /var/lib/fluree |
| Exposed port | 8090 |
| Runtime user | fluree (UID 1000, GID 1000) |
| Healthcheck | GET /health every 30s |
| Default log filter | RUST_LOG=info |
Entrypoint behavior: on first start, if /var/lib/fluree/.fluree/ does not exist, the entrypoint runs fluree init to create a default .fluree/config.toml and .fluree/storage/ directory. Subsequent starts skip init. Any arguments passed to docker run after the image name are forwarded to fluree server run, so you can append CLI flags (e.g. --log-level debug) directly.
docker run --rm -p 8090:8090 fluree/server:latest
Verify:
curl http://localhost:8090/health
Data lives inside the container's writable layer here — fine for trying things out, lost when the container is removed. For anything beyond a smoke test, mount a volume.
The image declares VOLUME /var/lib/fluree. Mount a host directory or named volume there:
# Named volume (recommended)
docker run -d --name fluree \
-p 8090:8090 \
-v fluree-data:/var/lib/fluree \
fluree/server:latest
# Host bind mount — make sure the directory is writable by UID 1000
mkdir -p ./fluree-data && sudo chown 1000:1000 ./fluree-data
docker run -d --name fluree \
-p 8090:8090 \
-v "$PWD/fluree-data:/var/lib/fluree" \
fluree/server:latest
The volume holds both .fluree/config.toml (config) and .fluree/storage/ (ledger data) by default.
Fluree resolves configuration with this precedence (highest wins):
FLUREE_*) set with -e or environment:[profiles.<name>.server]) when you pass --profile.fluree/config.toml or .fluree/config.jsonldYou can use any one of these — or, more typically, layer them: bake a base config file into a volume, then tweak per-environment with env vars or compose overrides.
Heads up — log level: The Dockerfile sets
ENV RUST_LOG=info. The console log filter usesRUST_LOGif it is non-empty and only falls back toFLUREE_LOG_LEVELwhenRUST_LOGis unset. Inside this image you must overrideRUST_LOGto change console verbosity:docker run -e RUST_LOG=debug fluree/server:latest
Every CLI flag has a FLUREE_* env var equivalent (see Configuration). For simple deployments this is the lowest-friction path:
docker run -d --name fluree \
-p 8090:8090 \
-v fluree-data:/var/lib/fluree \
-e FLUREE_LISTEN_ADDR=0.0.0.0:8090 \
-e FLUREE_STORAGE_PATH=/var/lib/fluree/.fluree/storage \
-e FLUREE_INDEXING_ENABLED=true \
-e FLUREE_REINDEX_MIN_BYTES=1000000 \
-e FLUREE_REINDEX_MAX_BYTES=10000000 \
-e FLUREE_CACHE_MAX_MB=2048 \
-e RUST_LOG=info \
fluree/server:latest
Author a config file on the host, then mount it at /var/lib/fluree/.fluree/config.jsonld (or .toml). The server walks up from WORKDIR=/var/lib/fluree and picks it up automatically.
./fluree-config/config.jsonld:
{
"@context": { "@vocab": "https://ns.flur.ee/config#" },
"server": {
"listen_addr": "0.0.0.0:8090",
"storage_path": "/var/lib/fluree/.fluree/storage",
"log_level": "info",
"cache_max_mb": 2048,
"indexing": {
"enabled": true,
"reindex_min_bytes": 1000000,
"reindex_max_bytes": 10000000
}
},
"profiles": {
"prod": {
"server": {
"log_level": "warn",
"cache_max_mb": 8192
}
}
}
}
docker run -d --name fluree \
-p 8090:8090 \
-v fluree-data:/var/lib/fluree \
-v "$PWD/fluree-config/config.jsonld:/var/lib/fluree/.fluree/config.jsonld:ro" \
fluree/server:latest --profile prod
If both config.toml and config.jsonld exist in the same directory, TOML wins and the server logs a warning. Pick one format.
The TOML equivalent (./fluree-config/config.toml):
[server]
listen_addr = "0.0.0.0:8090"
storage_path = "/var/lib/fluree/.fluree/storage"
log_level = "info"
cache_max_mb = 2048
[server.indexing]
enabled = true
reindex_min_bytes = 1000000
reindex_max_bytes = 10000000
[profiles.prod.server]
log_level = "warn"
cache_max_mb = 8192
You can also stash the config outside WORKDIR and point at it explicitly:
docker run -d --name fluree \
-p 8090:8090 \
-v fluree-data:/var/lib/fluree \
-v "$PWD/fluree-config:/etc/fluree:ro" \
fluree/server:latest --config /etc/fluree/config.jsonld
The common production shape: bake the base config into the image or volume, then let the orchestrator override per-environment with FLUREE_* env vars. Env vars beat the file — no file edit needed to bump cache size in staging vs. prod.
docker run -d --name fluree \
-p 8090:8090 \
-v fluree-data:/var/lib/fluree \
-v "$PWD/fluree-config/config.jsonld:/var/lib/fluree/.fluree/config.jsonld:ro" \
-e FLUREE_CACHE_MAX_MB=4096 \
-e RUST_LOG=warn \
fluree/server:latest
cache_max_mb is the global budget for the in-memory index/flake cache. The default is tiered by effective system memory: <4GB uses 30%, 4-8GB uses 40%, and >=8GB uses 35%. On a container with a hard memory limit, the auto-tier clamps to the cgroup limit when available; set this explicitly if you need a tighter cache budget.
# docker-compose.yml fragment
services:
fluree:
image: fluree/server:latest
mem_limit: 6g
environment:
FLUREE_CACHE_MAX_MB: 3072 # ~50% of the cgroup limit
Or in JSON-LD:
{
"@context": { "@vocab": "https://ns.flur.ee/config#" },
"server": { "cache_max_mb": 3072 }
}
Indexing is off by default. Enable it for production write workloads — without it, every commit writes to novelty and queries get slower as novelty grows.
| Setting | Meaning |
|---|---|
indexing.enabled | Turn the background indexer on |
reindex_min_bytes | Soft threshold — novelty above this triggers a background reindex |
reindex_max_bytes | Hard threshold — commits block above this until reindexing catches up |
Tune min/max based on commit volume. Defaults (100 KB / 1 MB) are conservative; busy ledgers should raise both:
[server.indexing]
enabled = true
reindex_min_bytes = 5000000 # 5 MB — start indexing in the background
reindex_max_bytes = 50000000 # 50 MB — block commits at this point
docker run -d \
-e FLUREE_INDEXING_ENABLED=true \
-e FLUREE_REINDEX_MIN_BYTES=5000000 \
-e FLUREE_REINDEX_MAX_BYTES=50000000 \
fluree/server:latest
[server]
cors_enabled = true
body_limit = 104857600 # 100 MB — raise for bulk imports
Require a Bearer token on data and admin endpoints. The trusted issuer is the did:key of your token signer.
[server.auth.data]
mode = "required"
trusted_issuers = ["did:key:z6Mk..."]
[server.auth.admin]
mode = "required"
trusted_issuers = ["did:key:z6Mk..."]
For OIDC/JWKS (e.g. an external IdP), set --jwks-issuer or FLUREE_JWKS_ISSUERS:
docker run -d \
-e FLUREE_DATA_AUTH_MODE=required \
-e FLUREE_JWKS_ISSUERS="https://auth.example.com=https://auth.example.com/.well-known/jwks.json" \
fluree/server:latest
See Configuration → Authentication for the full matrix.
For multi-node or cloud deployments, point the server at a JSON-LD connection config describing your storage and nameservice. AWS credentials come from the standard SDK chain (env vars, IAM role, etc.) — they are not part of the connection config.
./fluree-config/connection.jsonld:
{
"@context": {
"@base": "https://ns.flur.ee/config/connection/",
"@vocab": "https://ns.flur.ee/system#"
},
"@graph": [
{ "@id": "commitStorage", "@type": "Storage",
"s3Bucket": "fluree-prod-commits", "s3Prefix": "data/" },
{ "@id": "indexStorage", "@type": "Storage",
"s3Bucket": "fluree-prod-indexes" },
{ "@id": "publisher", "@type": "Publisher",
"dynamodbTable": "fluree-nameservice", "dynamodbRegion": "us-east-1" },
{ "@id": "conn", "@type": "Connection",
"commitStorage": { "@id": "commitStorage" },
"indexStorage": { "@id": "indexStorage" },
"primaryPublisher": { "@id": "publisher" } }
]
}
docker run -d --name fluree \
-p 8090:8090 \
-v "$PWD/fluree-config:/etc/fluree:ro" \
-e AWS_REGION=us-east-1 \
-e AWS_ACCESS_KEY_ID=... \
-e AWS_SECRET_ACCESS_KEY=... \
-e FLUREE_CONNECTION_CONFIG=/etc/fluree/connection.jsonld \
-e FLUREE_INDEXING_ENABLED=true \
fluree/server:latest
--connection-config and --storage-path are mutually exclusive. See Configuration → Connection Configuration and the DynamoDB guide for backend-specific setup.
fluree-search-httpd)Run a dedicated BM25 / vector search service alongside the main server when search traffic is heavy enough that you want it isolated from the transactional path. The service is a separate binary with its own listen port — it is not mounted under the main server's api_base_url. It needs read access to the same storage and nameservice paths the main server writes to.
docker run -d --name fluree-search \
-p 9090:9090 \
-v fluree-data:/var/lib/fluree \
-e FLUREE_STORAGE_ROOT=/var/lib/fluree/storage \
-e FLUREE_NAMESERVICE_PATH=/var/lib/fluree/ns \
fluree/search-httpd:latest
| Env var | Default | Purpose |
|---|---|---|
FLUREE_STORAGE_ROOT | (required) | Storage path (file:// optional) |
FLUREE_NAMESERVICE_PATH | (required) | Nameservice path |
FLUREE_SEARCH_LISTEN | 0.0.0.0:9090 | Listen address |
FLUREE_SEARCH_CACHE_MAX_ENTRIES | 100 | Max cached indexes |
FLUREE_SEARCH_CACHE_TTL_SECS | 300 | Cache TTL |
FLUREE_SEARCH_MAX_LIMIT | 1000 | Max results per query |
FLUREE_SEARCH_DEFAULT_TIMEOUT_MS | 30000 | Default request timeout |
FLUREE_SEARCH_MAX_TIMEOUT_MS | 300000 | Maximum allowed timeout |
Prerequisites. The service only serves queries against indexes that already exist on the shared volume. BM25 / vector graph-source indexes are created via the Rust API today (Bm25CreateConfig + create_full_text_index, or VectorCreateConfig + create_vector_index). The @fulltext datatype and the f:fullTextDefaults config-graph paths are managed entirely through the main server's HTTP API and don't require this dedicated service.
Compose example with both services sharing a volume:
services:
fluree:
image: fluree/server:latest
ports:
- "8090:8090"
volumes:
- fluree-data:/var/lib/fluree
environment:
RUST_LOG: info
FLUREE_INDEXING_ENABLED: "true"
fluree-search:
image: fluree/search-httpd:latest
depends_on:
- fluree
ports:
- "9090:9090"
volumes:
- fluree-data:/var/lib/fluree:ro # read-only is sufficient
environment:
RUST_LOG: info
FLUREE_STORAGE_ROOT: /var/lib/fluree/storage
FLUREE_NAMESERVICE_PATH: /var/lib/fluree/ns
volumes:
fluree-data:
Clients send search requests to POST http://fluree-search:9090/v1/search. See BM25 → Remote Search Service for the request/response protocol.
Run as a read-only peer that subscribes to a transaction server's event stream:
docker run -d --name fluree-peer \
-p 8090:8090 \
-v fluree-peer-data:/var/lib/fluree \
-e FLUREE_SERVER_ROLE=peer \
-e FLUREE_TX_SERVER_URL=http://tx.internal:8090 \
fluree/server:latest --peer-subscribe-all
See Query peers and replication for the proxy-mode and auth options.
A production-leaning single-node setup with a mounted JSON-LD config, env-var overrides, named data volume, and resource limits:
services:
fluree:
image: fluree/server:latest
container_name: fluree
restart: unless-stopped
ports:
- "8090:8090"
volumes:
- fluree-data:/var/lib/fluree
- ./fluree-config/config.jsonld:/var/lib/fluree/.fluree/config.jsonld:ro
environment:
RUST_LOG: info
FLUREE_CACHE_MAX_MB: 4096
FLUREE_INDEXING_ENABLED: "true"
FLUREE_REINDEX_MIN_BYTES: "5000000"
FLUREE_REINDEX_MAX_BYTES: "50000000"
# Auth — point at your trusted did:key signer
FLUREE_DATA_AUTH_MODE: required
FLUREE_DATA_AUTH_TRUSTED_ISSUERS: did:key:z6Mk...
FLUREE_ADMIN_AUTH_MODE: required
FLUREE_ADMIN_AUTH_TRUSTED_ISSUERS: did:key:z6Mk...
mem_limit: 8g
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8090/health"]
interval: 30s
timeout: 3s
start_period: 15s
retries: 3
command: ["--profile", "prod"]
volumes:
fluree-data:
docker compose up -d
docker compose logs -f fluree
Container restarts after fluree init. First-run init only runs when /var/lib/fluree/.fluree/ is missing. If the volume is owned by a non-1000 UID, init fails. Fix with sudo chown -R 1000:1000 ./fluree-data on the host.
Mounted config file is ignored. Confirm the mount path and the file extension. The server only auto-discovers .fluree/config.toml or .fluree/config.jsonld under the working directory. Anything else needs --config <path> (or FLUREE_CONFIG=<path>). If both formats are present in the same directory, TOML wins — check the startup logs for the warning.
Setting FLUREE_LOG_LEVEL doesn't change console output. The image's ENV RUST_LOG=info shadows it. Override with -e RUST_LOG=debug instead.
cache_max_mb auto-default is too large under a memory limit. Current builds clamp the auto-tier to the cgroup limit when available. If logs still show an oversized cache, check for an older image/binary or an explicit FLUREE_CACHE_MAX_MB / cache_max_mb override.
Health check failing. curl http://localhost:8090/health from your host. If the server is up but the healthcheck fails, the listen address is probably bound to 127.0.0.1 inside the container — set FLUREE_LISTEN_ADDR=0.0.0.0:8090.
Content type
Image
Digest
sha256:14da004ab…
Size
80.7 MB
Last updated
5 days ago
docker pull fluree/server