Self-hosting
One command
cp .env.example .env # then set SODAMEM_API_KEY
docker compose up -dThat builds the image, starts the server on http://localhost:8000, serves the web console at http://localhost:8000/console, and persists all data in a named Docker volume (sodamem-data, mounted at /data inside the container). Nothing is written to the host filesystem directly, and nothing survives only in the container's writable layer.
The console is compiled inside the image in a dedicated console-builder stage, so nothing on the host needs Node installed.
Running outside Docker is different
console/dist will not exist until you run npm install && npm run build in console/. Until then the API starts normally and just logs that the console is not mounted.
Auth is on by default
docker-compose.yml never sets SODAMEM_AUTH_DISABLED. The server refuses to start if SODAMEM_API_KEY is unset, so there is no accidentally-open deployment. Set the key in .env before the first docker compose up.
Every other knob is documented with its default in Environment variables.
Run exactly one worker
--workers 1 is a correctness constraint, not a throughput setting. Per-user stores are SQLite databases opened without WAL, and two processes writing the same user's store corrupt it.
The shipped CMD states it explicitly, and the server takes an exclusive lock on its data root at startup — a second process pointed at the same directory refuses to start with data_root_locked. Horizontal scaling needs an external job store first (ADR 0001).
Calling it
# liveness — unauthenticated, touches no store
curl http://localhost:8000/health
# {"status":"ok","version":"0.1.1","schema_version":1,"auth":"enabled"}
# a real endpoint needs the API key (Authorization: Bearer, or X-API-Key)
curl http://localhost:8000/v1/search \
-X POST -H "Content-Type: application/json" \
-H "Authorization: Bearer $SODAMEM_API_KEY" \
-d '{"user_id":"alice","query":"favorite color"}'Swagger UI is served at /docs on the container once it is up. The full route list is in the HTTP API reference.
Operating it
/v1/admin/* answers the questions that otherwise need a shell inside the container. The web console's Ops page is the same data with a UI.
# effective configuration — every secret reported as set/not-set, never masked
curl -H "Authorization: Bearer $SODAMEM_API_KEY" localhost:8000/v1/admin/config
# mint a named key; the plaintext is returned ONCE and is not recoverable
curl -X POST -H "Authorization: Bearer $SODAMEM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"ci-pipeline"}' localhost:8000/v1/admin/keys
# who called what, most recent first (rolling window, not an archive)
curl -H "Authorization: Bearer $SODAMEM_API_KEY" localhost:8000/v1/admin/requests
# disk + workload shape
curl -H "Authorization: Bearer $SODAMEM_API_KEY" localhost:8000/v1/admin/statsNamed keys exist for attribution, not isolation: every request records which key made it, so "who is hammering /v1/search" has an answer. There are no roles and no per-key scopes — any live key can read ops data and manage other keys. SODAMEM_API_KEY keeps working exactly as before and cannot be revoked through the API, which makes it the way back in if every named key is revoked.
Running without Compose
docker build -t sodamem .
docker run -d \
-e SODAMEM_API_KEY=... \
-p 8000:8000 \
-v sodamem-data:/data \
sodamemNext
- Metrics and cost — what to scrape and why
- Backup and upgrade
- Environment variables

