Deploying on Docker
Running Curiosity Workspace as a Docker container is the most common way to install it. The same image runs in local dev, in CI, and in production behind a reverse proxy.
This page covers single-host Docker deployments. For Kubernetes, see Kubernetes; for managed cloud variants, see AWS, Azure, GCP, and OpenShift.
Requirements
- A 64-bit Linux, macOS, or Windows host with Docker installed. Follow the official Docker docs.
- 8 GB of RAM available to Docker (16 GB+ for production with embeddings).
- A persistent volume mount for
MSK_GRAPH_STORAGE. - A network plan: bind to
127.0.0.1for local dev, put a TLS-terminating proxy in front for shared deployments.
Pulling the image
The image is published as curiosityai/curiosity:
docker pull curiosityai/curiosity:latest
To pin to a specific release:
docker pull curiosityai/curiosity:v1.42.0
Pin in production
:latest will roll forward on every deploy. Pin to a versioned tag in production (and staging) so upgrades are explicit. See Upgrades and migrations.
Local development — single docker run
The simplest invocation, suitable only for a developer's own machine:
mkdir -p ~/curiosity/storage
docker run --name curiosity \
-p 127.0.0.1:8080:8080 \
-v ~/curiosity/storage:/data \
-e MSK_GRAPH_STORAGE=/data/curiosity \
-e MSK_ADMIN_PASSWORD="$(openssl rand -base64 24)" \
curiosityai/curiosity:latest
Notes:
127.0.0.1:8080:8080binds the workspace to loopback only. Drop the IP prefix when you're ready to expose it on the host network.MSK_ADMIN_PASSWORDreplaces the defaultadmin/admin. Read it back from your shell history ordocker inspect.
On Windows, swap the volume syntax:
mkdir c:\curiosity\storage
docker run --name curiosity `
-p 127.0.0.1:8080:8080 `
-v c:/curiosity/storage:/data `
-e MSK_GRAPH_STORAGE=/data/curiosity `
-e MSK_ADMIN_PASSWORD=ChangeMeNow! `
curiosityai/curiosity:latest
Docker on Windows expects forward slashes inside volume paths.
Staging / production — Docker Compose
For anything beyond local dev, use Docker Compose so the configuration is versioned and reproducible.
docker-compose.yml:
services:
curiosity:
image: curiosityai/curiosity:v1.42.0
container_name: curiosity
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080" # exposed via the reverse proxy below
volumes:
- curiosity-data:/data
- curiosity-backups:/backups
- ./certs:/certs:ro
environment:
MSK_GRAPH_STORAGE: /data/curiosity
MSK_GRAPH_BACKUP_FOLDER: /backups
MSK_GRAPH_JOURNAL_FOLDER: /data/journal
MSK_PUBLIC_ADDRESS: https://workspace.example.com
MSK_USE_HSTS: "true"
MSK_REDIRECT_TO_HTTPS: "true"
# Secrets injected via env_file (kept out of git)
env_file:
- .env
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8080/api/login/check", "-o", "/dev/null"]
interval: 30s
timeout: 5s
retries: 5
start_period: 60s
volumes:
curiosity-data:
curiosity-backups:
.env (kept out of source control — see your secret manager):
MSK_ADMIN_PASSWORD=<from secret manager>
MSK_ADMIN_EMAIL=ops@example.com
MSK_JWT_KEY=<32+ random bytes, from secret manager>
MSK_LICENSE=<your license token>
MSK_GRAPH_MASTER_KEY=<32+ random bytes, backed up separately>
Bring it up:
docker compose up -d
docker compose logs -f curiosity
Production checklist
A short list of must-haves before a Docker-based workspace serves real users:
- Versioned image tag (
curiosityai/curiosity:vX.Y.Z), not:latest. - Persistent volume for
MSK_GRAPH_STORAGEon durable storage (SSD or better). - Backup volume for
MSK_GRAPH_BACKUP_FOLDER, off-host on a schedule. - All secrets injected from a secret manager (never baked into the image or compose file).
- TLS terminated at a reverse proxy or in-container (set
MSK_CERT_FILE,MSK_CERT_FILE_PRIVATE_KEY,MSK_USE_HSTS=true,MSK_REDIRECT_TO_HTTPS=true). -
MSK_PUBLIC_ADDRESSset so generated links use the right host. - Container memory/CPU limits sized for embedding workload (start at 16 GB / 8 vCPU).
- Healthcheck enabled so the orchestrator can restart the container on hangs.
- Log routing to your aggregator — either
MSK_LOG_PATHon a mounted volume or the platform's stdout collector. - Backup/restore drill completed and documented.
See Production deployment for the broader checklist that also covers identity, monitoring, and disaster recovery.
Healthcheck details
The healthcheck above pings /api/login/check, which returns 401 (no token attached) once the workspace is accepting traffic and 5xx while it's still booting. Curl's -f flag treats 401 as success — that's intentional, because reaching the auth layer is exactly what we want to verify.
Upgrading
Pinning a version means upgrades are deliberate:
docker compose pull
docker compose up -d
For breaking changes, follow Upgrades and migrations: take a backup first, run on staging, validate, then promote.
Common pitfalls
- Ephemeral storage — running without
-vor with a tmpfs mount loses data on container restart. Always mount a real volume onMSK_GRAPH_STORAGE. - Mixed
latestand pinned tags across environments leads to "works on staging" surprises. Be consistent. - Container time drift — embedding providers reject requests with skewed clocks. Run with the host's clock (default on Linux).
- No
MSK_PUBLIC_ADDRESSbehind a proxy — generated links (SSO callback URLs, email links) point at the internal hostname instead of the user-facing one. - Insufficient memory — the workspace process maps indexes into memory. OOM-killed containers leave the indexes in a recovery state on the next boot. Size accordingly.
See also
- Configuration reference — every
MSK_*variable. - Kubernetes — when you outgrow a single host.
- Backup and restore — what to do when something goes wrong.
- Troubleshooting — install and startup failures.