This walkthrough takes you from nothing to a running SirixDB REST server with a versioned resource, a JSONiq query, and a time-travel read + diff. Every command below was executed verbatim against the fat jar built from this tree; the output snippets are real.
There are three ways to run the server, fastest first:
| Option | Needs | Auth |
|---|---|---|
| A. Dev mode, no Keycloak | Java 25 + the fat jar | none (auth.mode=none) |
| B. Fat jar + Keycloak | Java 25, Docker | OAuth2 (Keycloak) |
| C. Everything in Docker | Docker | OAuth2 (Keycloak) |
Getting the fat jar (used by options A and B):
# Either build it from source (requires JDK 25):
./gradlew :sirix-rest-api:shadowJar
# -> bundles/sirix-rest-api/build/libs/sirix-rest-api-<version>-fat.jar
# ...or download it from a GitHub release:
# https://github.com/sirixdb/sirix/releases
The fat jar is compiled with --enable-preview, which pins it to the exact JDK
major version it was built with — use a Java 25 JVM (java -version).
Requires
auth.mode=nonesupport (any build of this tree newer than 1.0.0-alpha22). In this mode authentication is disabled: every request — with or without a token — runs as an admin user with all permissions. The server logs a loud warning. Local development only; never expose it.
cat > sirix-dev-conf.json <<'EOF'
{
"port": 9443,
"use.http": true,
"auth.mode": "none"
}
EOF
java -Xms256m -Xmx2g -XX:MaxDirectMemorySize=2g \
--enable-preview --enable-native-access=ALL-UNNAMED \
--add-modules=jdk.incubator.vector \
--add-exports=java.base/jdk.internal.ref=ALL-UNNAMED \
--add-exports=java.base/sun.nio.ch=ALL-UNNAMED \
--add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
--add-opens=java.base/java.io=ALL-UNNAMED \
--add-opens=java.base/java.util=ALL-UNNAMED \
-jar bundles/sirix-rest-api/build/libs/sirix-rest-api-*-fat.jar \
-conf sirix-dev-conf.json
(SIRIX_AUTH_MODE=none as an environment variable works too and overrides the
config file.) Databases are stored under $HOME/sirix-data/; pass
-Duser.home=/some/dir to relocate them. The startup log shows:
WARN io.sirix.rest.SirixVerticle - * AUTHENTICATION IS DISABLED (auth.mode=none). *
curl -s http://localhost:9443/health
{"status":"UP"}
curl -s -X PUT http://localhost:9443/mydb/products \
-H "Content-Type: application/json" \
-d '[{"id":1,"name":"keyboard","price":49.9},{"id":2,"name":"mouse","price":19.9}]'
[{"id":1,"name":"keyboard","price":49.9},{"id":2,"name":"mouse","price":19.9}]
curl -s -X POST http://localhost:9443/ \
-H "Content-Type: application/json" \
-d '{"query":"for $p in jn:doc('\''mydb'\'','\''products'\'')[] where $p.price lt 50 return $p.name"}'
{"rest":[{"revisionNumber":1,"revisionTimestamp":"...","revision":{"name":"keyboard"}},
{"revisionNumber":1,"revisionTimestamp":"...","revision":{"name":"mouse"}}]}
# JSONiq update queries auto-commit — this creates revision 2:
curl -s -X POST http://localhost:9443/ \
-H "Content-Type: application/json" \
-d '{"query":"insert json {\"name\": \"monitor\", \"id\": 3, \"price\": 199.0} into jn:doc('\''mydb'\'','\''products'\'')"}'
# Latest revision now has three products:
curl -s http://localhost:9443/mydb/products
# -> [{"id":1,...},{"id":2,...},{"name":"monitor","id":3,"price":199}]
# Revision 1 is still there, unchanged:
curl -s "http://localhost:9443/mydb/products?revision=1"
# -> [{"id":1,"name":"keyboard","price":49.9},{"id":2,"name":"mouse","price":19.9}]
# Or time travel inside a query — count items per revision:
curl -s -X POST http://localhost:9443/ -H "Content-Type: application/json" \
-d '{"query":"count(jn:doc('\''mydb'\'','\''products'\'', 1)[])"}' # -> {"rest":[2]}
curl -s -X POST http://localhost:9443/ -H "Content-Type: application/json" \
-d '{"query":"count(jn:doc('\''mydb'\'','\''products'\'')[])"}' # -> {"rest":[3]}
curl -s http://localhost:9443/mydb/products/history
{"history":[
{"revision":2,"revisionTimestamp":"2026-06-11T13:15:23.069Z","author":"admin","commitMessage":""},
{"revision":1,"revisionTimestamp":"2026-06-11T13:15:08.577Z","author":"admin","commitMessage":""}]}
curl -s "http://localhost:9443/mydb/products/diff?first-revision=1&second-revision=2"
{"database":"mydb","resource":"products","old-revision":1,"new-revision":2,
"diffs":[{"insert":{"nodeKey":10,"insertPositionNodeKey":6,
"insertPosition":"asRightSibling","path":"/[2]","type":"jsonFragment"}}]}
That’s the whole loop: create → query → commit → time travel → diff.
The default and production configuration: all endpoints (except /health and
/metrics) require an OAuth2 bearer token.
The repository ships a pre-configured Keycloak (realm sirixdb, client sirix,
users admin/admin with full access and viewer/viewer read-only):
git clone https://github.com/sirixdb/sirix.git && cd sirix
docker compose up -d keycloak # first run builds the image and takes ~1 minute
Wait until the realm answers:
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080/realms/sirixdb # -> 200
bundles/sirix-rest-api/src/main/resources/sirix-conf.json already points at
http://localhost:8080/realms/sirixdb:
java -Xms256m -Xmx2g -XX:MaxDirectMemorySize=2g \
--enable-preview --enable-native-access=ALL-UNNAMED \
--add-modules=jdk.incubator.vector \
--add-exports=java.base/jdk.internal.ref=ALL-UNNAMED \
--add-exports=java.base/sun.nio.ch=ALL-UNNAMED \
--add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
--add-opens=java.base/java.io=ALL-UNNAMED \
--add-opens=java.base/java.util=ALL-UNNAMED \
-jar bundles/sirix-rest-api/build/libs/sirix-rest-api-*-fat.jar \
-conf bundles/sirix-rest-api/src/main/resources/sirix-conf.json
If Keycloak is not reachable the server now fails fast with an actionable error
(start Keycloak, or use auth.mode=none) instead of an opaque stack trace.
TOKEN=$(curl -s -X POST http://localhost:9443/token \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin","grant_type":"password"}' | jq -r .access_token)
curl -s -X PUT http://localhost:9443/mydb/products \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '[{"id":1,"name":"keyboard","price":49.9},{"id":2,"name":"mouse","price":19.9}]'
All requests are identical to Option A plus the
-H "Authorization: Bearer $TOKEN" header. Expected outputs are the same.
Tokens expire after a few minutes — re-run the TOKEN=$(...) line if you get
401 Unauthorized.
git clone https://github.com/sirixdb/sirix.git && cd sirix
docker compose up -d # builds + starts Keycloak and the server
curl -s http://localhost:9443/health # -> {"status":"UP"} once healthy
Then continue exactly like Option B step 3.
How the official Docker image differs from running the fat jar yourself:
sirix-docker-conf.json sets
use.http: true). No -k/TLS needed for local use; put a TLS-terminating
proxy in front for anything public.http://keycloak:8080/...
(the compose service name), not localhost.SIRIX_XMS (default 256m), SIRIX_XMX (default 2g),
SIRIX_MAX_DIRECT (default 2g), and SIRIX_JAVA_OPTS for any extra flags
— e.g. docker run -e SIRIX_XMX=8g .... The defaults fit a laptop; raise
them for big imports or heavy concurrency.docker run -e SIRIX_AUTH_MODE=none -p 9443:9443
sirixdb/sirix:latest gives you Option A’s no-Keycloak dev mode in Docker./opt/sirix/sirix-data inside the container — mount a
volume (-v sirix-data:/opt/sirix/sirix-data) to persist it.Unsupported class file major version / preview-feature errors — your
java is not version 25. The fat jar requires the JDK major version it was
built with.keycloak.url is wrong). Start it (docker compose up -d keycloak)
or use auth.mode=none for local development.401 Unauthorized — token missing or expired; fetch a fresh one from
POST /token.403 Forbidden — the user lacks the role for that operation (create,
modify, view, delete — global or per-database <db>-<role> roles).$HOME/sirix-data/ (fat jar) or
/opt/sirix/sirix-data (Docker).For production deployment (TLS, real users, secret rotation, memory budgets),
see operations.md.