Curiosity

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.1 for 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:nightly

To pin to a specific release, use the numeric build tag:

docker pull curiosityai/curiosity:66474
Pin in production

:nightly rolls forward on every push. Pin to a numeric build 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:nightly

Notes:

  • 127.0.0.1:8080:8080 binds the workspace to loopback only. Drop the IP prefix when you're ready to expose it on the host network.
  • MSK_ADMIN_PASSWORD replaces the default admin/admin. Read it back from your shell history or docker 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:nightly

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:66474
    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

Environment variables

The container is configured almost entirely through MSK_* environment variables — there is no file-based config inside the image. Pass them with -e flags on docker run, an environment: block in Compose, or an env_file: for secrets. The tables below cover the variables you're most likely to set on a Docker host; see Configuration reference for the full list including integration-specific keys (OpenAI, S3, Azure OCR, etc.).

Booleans

Boolean variables accept true / false (case-insensitive). In Compose, quote them ("true") so YAML doesn't coerce them to native booleans before they reach the container.

Storage and data

Variable Type Default Description
MSK_GRAPH_STORAGE path system temp Primary on-disk location for the graph database. Mount a persistent volume here.
MSK_GRAPH_BACKUP_FOLDER path Destination for graph snapshots. Mount a separate volume for off-host backup.
MSK_GRAPH_TEMP_FOLDER path system temp Scratch space for graph operations.
MSK_GRAPH_RESTORE_FROM_INDEX int -1 If set, restore the graph from this backup index on startup.
MSK_GRAPH_CHECK_INTEGRITY bool false Verify graph integrity on boot (slower startup).
MSK_GRAPH_CHECK_BLOBS_INTEGRITY bool false Verify blob-store integrity on boot.
MSK_ROCKSDB_CACHE_SIZE_MB int RocksDB block-cache size in MB. Tune for available RAM.

Networking and addresses

Variable Type Default Description
MSK_PORT int 8080 HTTP(S) listening port inside the container.
MSK_PUBLIC_ADDRESS URL Public-facing URL (e.g. https://workspace.example.com). Used to generate links in emails and SSO callbacks.
MSK_SERVER_ADDRESS URL Internal address used by components when it differs from the public address.
MSK_SERVER_BASE_PATH string URL base path when mounted under a subpath of the reverse proxy.
MSK_CORS string Comma-separated list of additional CORS origins.
MSK_DISABLE_CORS_LOCALHOST bool false Drop localhost from the default CORS allowlist.
MSK_MAX_BODY_SIZE int (MB) 512 Maximum request body size in megabytes.
MSK_HTTP_PROXY URL Outbound HTTP proxy address.
MSK_HTTP_PROXY_USER string Outbound proxy username.
MSK_HTTP_PROXY_PASSWORD string Outbound proxy password.

TLS and certificates

Variable Type Default Description
MSK_CERT_FILE path Path to the TLS certificate (PEM) inside the container.
MSK_CERT_FILE_PRIVATE_KEY path Path to the matching private key.
MSK_CERT_PWD string Password for an encrypted certificate / private key.
MSK_CERT_FROM_STORE_NAME string Load certificate by name from the OS certificate store (rare in containers).
MSK_CERT_SELF_SIGNED bool false Generate and use a self-signed certificate on boot. Dev only.
MSK_LETS_ENCRYPT_SUBDOMAIN string Subdomain for in-container Let's Encrypt issuance.
MSK_USE_HSTS bool false Send HSTS headers. Enable once TLS is stable.
MSK_REDIRECT_TO_HTTPS bool false Redirect plain HTTP to HTTPS.

Authentication and security

Variable Type Default Description
MSK_JWT_KEY string Required. Signing key for session tokens. Use 32+ random bytes; rotate carefully.
MSK_GRAPH_MASTER_KEY string Required. Master encryption key for the graph database. Back up separately from data.
MSK_ADMIN_USER string admin Initial admin username created on first boot.
MSK_ADMIN_EMAIL string Initial admin email address.
MSK_ADMIN_PASSWORD string Initial admin password. Replace the admin/admin default.
MSK_ADMIN_INVITE string Comma-separated list of emails to invite as admins on first boot.
MSK_SAME_SITE_COOKIE bool true Set SameSite on auth cookies.
MSK_PASSWORDS_MIN_LENGTH int Minimum password length enforced on user changes.
MSK_PASSWORDS_USER_EXPIRATIONDAYS int 180 Days before a user password must be rotated.
MSK_PASSWORDS_ADMIN_EXPIRATIONDAYS int 90 Days before an admin password must be rotated.
MSK_LOGIN_EXP int (h) Session token lifetime in hours.
MSK_LOGIN_RENEW_EXP int (h) Refresh-window lifetime in hours.

Logging and diagnostics

Variable Type Default Description
MSK_LOG_LEVEL enum Information One of Trace, Debug, Information, Warning, Error.
MSK_LOG_PATH path Directory for application logs. Mount a volume to persist them.
MSK_AUDIT_LOG_PATH path Directory for the audit log (separate from application logs).
MSK_DISABLE_TELEMETRY bool false Disable anonymous usage telemetry.
MSK_DISABLE_CLOUD_ERROR_REPORT bool false Disable outbound crash reporting.
MSK_DISABLE_RESOURCE_MONITORING bool false Disable in-process memory/disk health checks.
MSK_RAM_THRESHOLD_UNHEALTHY string RAM threshold (e.g. 90%) above which the healthcheck reports unhealthy.
MSK_DISK_THRESHOLD_UNHEALTHY string Disk threshold above which the healthcheck reports unhealthy.

Replication and state tracking

Variable Type Default Description
MSK_REPLICA bool false Run as a read-only replica that follows a primary.
MSK_PRIMARY_ADDRESS URL URL of the primary workspace, required when MSK_REPLICA=true.
MSK_GIT_TRACK_STATE_PATH path Local path used to track admin/config state in git.
MSK_GIT_TRACK_STATE_BRANCH string main Branch used by the state tracker.
MSK_GIT_REMOTE_URL URL Remote repository for pushing tracked state.
MSK_GIT_REMOTE_USERNAME / MSK_GIT_REMOTE_PASSWORD string Credentials for the remote git repository.

In-product updates

These variables control the Software Updates admin page (Operate → Software Updates), which lets system admins pull a newer image tag and recreate the running container from the UI. See In-product updates below for the full flow and security trade-offs.

Variable Type Default Description
MSK_DISABLE_DOCKER_UPDATE bool false Hide the Software Updates page and refuse update requests. Set to true when upgrades are owned by an external orchestrator (Compose CI, Kubernetes, watchtower, etc.).
MSK_DOCKER_IMAGE_REPOSITORY string curiosityai/curiosity Registry repository to query for newer tags. Only override when running a private mirror or a fork. Must match ^[A-Za-z0-9][A-Za-z0-9_./-]{0,255}$.
MSK_DOCKER_SOCKET path /var/run/docker.sock Path to the Docker Engine socket as seen from inside the container. The conventional DOCKER_HOST=unix://… is also honoured.
MSK_CONTAINER_ID string auto-detected Override the detected container id. Auto-detection reads /proc/self/mountinfo and /proc/self/cgroup; set this when running under a runtime that hides the id (some rootless or PaaS setups).

Performance and runtime

Variable Type Default Description
MSK_LOW_MEMORY bool false Enable low-memory mode (smaller caches, slower queries).

Licensing and application metadata

Variable Type Description
MSK_LICENSE string License token issued by Curiosity.
MSK_LICENSED_TO string Customer name on the license.
MSK_APPNAME string Override the application display name.
MSK_FONTS_PATH path Directory of custom fonts to make available to renderers.
MSK_WWW_FOLDER path Override the static web-assets directory.

Production checklist

A short list of must-haves before a Docker-based workspace serves real users:

  • Numeric build tag (curiosityai/curiosity:66474), not :nightly.
  • Persistent volume for MSK_GRAPH_STORAGE on durable storage (SSD or better) - ideally with snapshot support.
  • Optional 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_ADDRESS set 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_PATH on 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

Use the /api/health endpoint to check application health. Use /api/ping to see if the container is responding to requests.

In-product updates

System admins can pull a newer image tag and recreate the running container from the Software Updates admin page (Operate → Software Updates), without dropping to the host shell. The page lists every numeric build tag in the configured repository that is newer than the running build; picking one triggers a watchtower-style cutover.

This is intended for small single-host deployments and lab environments. If upgrades are owned by Compose CI, Kubernetes, or an external watchtower, set MSK_DISABLE_DOCKER_UPDATE=true and ignore the page.

How the swap works

A container cannot replace itself — it holds the host ports the replacement needs and would have to keep issuing daemon calls after stopping. The workspace therefore delegates the cutover to a short-lived updater helper container spawned from its own image:

  1. The workspace pulls the chosen tag through the Docker Engine API.
  2. It inspects its own container to capture the current Config, HostConfig, and network attachments.
  3. It creates a helper container (named <original>-curiosity-updater, network none, AutoRemove: true) and starts it.
  4. The helper waits ~5 s so the HTTP response that triggered the update flushes, then stops + removes the old container and creates a replacement under the same name with the new image and the captured config.
  5. The daemon re-attaches the same networks. Static IPs (IPAMConfig) are preserved; service discovery by container name keeps working because the name is reused.

The workspace is unreachable for the duration of the swap — usually a few seconds, plus the new container's startup time.

Requirements

For the page to be functional:

  • The workspace must be running inside a container (/.dockerenv present, or DOTNET_RUNNING_IN_CONTAINER=true).
  • The Docker Engine socket must be bind-mounted into the container — by default at /var/run/docker.sock. Override the path with MSK_DOCKER_SOCKET.
  • The current user inside the container must be able to read/write the socket.
  • The configured MSK_DOCKER_IMAGE_REPOSITORY must be reachable on Docker Hub (or whichever registry serves it).

A minimal docker run:

docker run --name curiosity \
  -p 127.0.0.1:8080:8080 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v ~/curiosity/storage:/data \
  -e MSK_GRAPH_STORAGE=/data/curiosity \
  curiosityai/curiosity:66474

The only addition over the local development command is the second -v that bind-mounts the Docker socket.

The equivalent Compose snippet:

services:
  curiosity:
    image: curiosityai/curiosity:66474
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - curiosity-data:/data
    environment:
      MSK_GRAPH_STORAGE: /data/curiosity
      # Optional: pin a non-default repository (e.g. private mirror).
      # MSK_DOCKER_IMAGE_REPOSITORY: registry.example.com/curiosity/workspace
Mounting the Docker socket grants root-equivalent host access

Anything that can talk to /var/run/docker.sock can launch privileged containers, mount any host path, and read every other container on the host. Only mount it when you trust every system admin of the workspace — the page is gated by the AuthorizeSystemAdmin policy, but the socket itself has no finer-grained ACL. For multi-tenant or PCI/HIPAA environments, leave it unmounted and set MSK_DISABLE_DOCKER_UPDATE=true.

Picking a tag

Only numeric build tags are listed in the UI — latest and nightly are intentionally excluded because the page is about pinning to a concrete build. The dropdown shows tags newer than the running build, newest-first, with their push date and compressed size when the registry reports them.

The build number for the running container comes from MSK_BUILD_NUMBER, which is baked into every official image. If the workspace cannot determine the current build (e.g. when running an unofficial build), every tag in the repository is offered.

Disabling the feature

Set MSK_DISABLE_DOCKER_UPDATE=true. The admin sidebar entry disappears, the controller refuses status/list/update calls, and the helper-container path is unreachable. Use this whenever the deployment is managed by an external upgrade pipeline.

Troubleshooting

Symptom What to check
Page shows "Not running inside a Docker container — updates are managed by your installer." The container is not detected. Set DOTNET_RUNNING_IN_CONTAINER=true or run the official image.
Page shows "The Docker socket (…) is not reachable." Add -v /var/run/docker.sock:/var/run/docker.sock. If the socket lives elsewhere on the host, mount it and point MSK_DOCKER_SOCKET at the in-container path.
Page shows "Could not identify this container via the Docker API." Container id auto-detection failed. Set MSK_CONTAINER_ID to the long id from docker inspect.
Update appears to start but the container never comes back. Inspect the helper container's logs before it auto-removes: docker logs <name>-curiosity-updater. The helper prints [docker-swap] progress lines and the final error if the create/start step failed.
"Refusing to use suspicious image repository '…'." MSK_DOCKER_IMAGE_REPOSITORY must match ^[A-Za-z0-9][A-Za-z0-9_./-]{0,255}$. Remove tag/digest suffixes — the repository is the part before the :.

REST API

The page is a thin wrapper over three endpoints under /api/update/docker/, all gated by AuthorizeSystemAdmin. Useful for scripting upgrades without driving the UI:

Method Path Purpose
GET /api/update/docker/status Current container snapshot and whether a self-update is possible.
GET /api/update/docker/available Status plus the list of newer numeric tags.
POST /api/update/docker/update Body { "tag": "<tag>" } — kicks off the updater helper. Returns immediately; the swap happens a few seconds later.

Upgrading

Pinning a version means upgrades are deliberate. From the host:

docker compose pull
docker compose up -d

Or from inside the workspace, use the Software Updates admin page when the Docker socket is mounted.

For breaking changes, follow Upgrades and migrations: take a backup first, run on staging, validate, then promote.

Common pitfalls

  • Ephemeral storage — running without -v or with a tmpfs mount loses data on container restart. Always mount a real volume on MSK_GRAPH_STORAGE.
  • No MSK_PUBLIC_ADDRESS behind 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

© 2026 Curiosity. All rights reserved.
Powered by Neko