Skip to content

Docker Compose

NimbleBrain ships two container images: ghcr.io/nimblebraininc/nimblebrain-runtime (agent engine + API on port 27247, internal) and ghcr.io/nimblebraininc/nimblebrain-web (React UI served by Caddy, exposed on port 27246). Docker Compose runs both on a single machine with a shared internal network.

  • Docker Engine 24+ and Docker Compose v2
  • An Anthropic API key
  • At least 2 GB of available memory

Create a project directory and add this file:

docker-compose.yml
services:
platform:
image: ghcr.io/nimblebraininc/nimblebrain-runtime:latest
build: .
restart: unless-stopped
volumes:
- workspace:/data
- ./skills:/app/skills:ro
# The identity provider. The platform does not start without it.
- type: bind
source: ./instance.json
target: /data/instance.json
read_only: true
bind:
create_host_path: false
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
- GOOGLE_GENERATIVE_AI_API_KEY=${GOOGLE_GENERATIVE_AI_API_KEY:-}
- NEBIUS_API_KEY=${NEBIUS_API_KEY:-}
- XAI_API_KEY=${XAI_API_KEY:-}
networks:
- internal
web:
image: ghcr.io/nimblebraininc/nimblebrain-web:latest
build: web/
restart: unless-stopped
ports:
- "27246:8080"
environment:
- PLATFORM_URL=http://platform:27247
depends_on:
platform:
condition: service_healthy
networks:
- internal
volumes:
workspace:
networks:
internal:

The platform reads its working directory from the workspace volume mounted at /data (the image sets NB_WORK_DIR=/data). Configuration (nimblebrain.json), skills, and connector state all live under that directory — there is no separate config bind mount required to boot. Drop custom skill files into the ./skills directory if you want to mount them read-only.

Service Image Port Purpose
platform ghcr.io/nimblebraininc/nimblebrain-runtime 27247 (internal) Agent engine, HTTP API, MCP connector manager
web ghcr.io/nimblebraininc/nimblebrain-web 27246 (host) → 8080 (container) React SPA served by Caddy, proxies /v1/* to platform

The web container runs Caddy, which serves the static UI files and reverse-proxies all /v1/* API requests to the platform container. The platform container is not exposed to the host — all traffic flows through the web container.

Volume/Mount Container path Purpose
workspace (named volume) /data Working directory (NB_WORK_DIR) — conversations, logs, installed connectors, stored secrets, and nimblebrain.json
./skills (bind mount, read-only) /app/skills Custom skill files
Variable Required Description
ANTHROPIC_API_KEY Yes Your Anthropic API key for Claude
OPENAI_API_KEY No OpenAI API key (for OpenAI model provider)
GOOGLE_GENERATIVE_AI_API_KEY No Google Gemini API key (for Google model provider)
NEBIUS_API_KEY No Nebius Token Factory API key (for the Nebius model provider)
XAI_API_KEY No xAI API key (for the xAI/Grok model provider)
ALLOWED_ORIGINS Production Comma-separated list of allowed CORS origins (e.g., https://nb.example.com). Required for cross-origin cookie auth
NB_PUBLIC_ORIGIN Production (when using OAuth connectors) Canonical public origin of the platform (e.g., https://nb.example.com). Used to build absolute callback URLs such as the OAuth redirect_uri for connectors. A mismatch with the URL registered in each vendor’s OAuth app produces vendor-side redirect_uri does not match errors at sign-in time.

Authentication is configured through an instance.json adapter (dev, oidc, or workos), not an environment variable. There is no NB_API_KEY — see Security for adapter setup.

The platform image has a built-in health check:

HEALTHCHECK --interval=30s --timeout=5s \
CMD curl -f http://localhost:27247/v1/health || exit 1

The web service uses depends_on with condition: service_healthy, so it only starts after the platform passes its first health check.

  1. Set environment variables

    Create a .env file in your project directory:

    .env
    ANTHROPIC_API_KEY=sk-ant-api03-your-key-here

    Or export it directly:

    Terminal window
    export ANTHROPIC_API_KEY=sk-ant-api03-your-key-here

    Then write instance.json next to docker-compose.yml. For a local trial, the dev adapter:

    Terminal window
    echo '{"auth":{"adapter":"dev"}}' > instance.json

    It checks no credential. Configure a real adapter before exposing the platform to a network — see Security.

  2. Start the services

    Terminal window
    docker compose up -d
    ✔ Network project_internal Created
    ✔ Volume "project_workspace" Created
    ✔ Container project-platform-1 Healthy
    ✔ Container project-web-1 Started
  3. Verify

    Terminal window
    docker compose ps
    NAME STATUS PORTS
    project-platform-1 running (healthy) 27247/tcp
    project-web-1 running 0.0.0.0:27246->8080/tcp

    Check the health endpoint directly (proxied through the web container):

    Terminal window
    curl http://localhost:27246/v1/health
    {"status":"ok"}
  4. Open the UI

    Go to http://localhost:27246. With the dev adapter the UI opens straight to the agent. With a real adapter, you sign in through that adapter’s flow (for example the OIDC provider’s hosted login).

Terminal window
# Stop (preserves volumes)
docker compose down
# Stop and remove volumes (deletes all workspace data)
docker compose down -v
# Restart
docker compose restart
  1. Pull the latest images

    Terminal window
    docker compose pull
  2. Recreate the containers

    Terminal window
    docker compose up -d

    Compose replaces only the containers whose images changed. The workspace volume persists across updates.

Workspace data lives in the workspace named Docker volume. The default mount point is /data inside the platform container.

Terminal window
docker run --rm \
-v project_workspace:/data \
-v "$(pwd)":/backup \
alpine tar czf /backup/nimblebrain-backup.tar.gz -C /data .
Terminal window
docker compose down
docker run --rm \
-v project_workspace:/data \
-v "$(pwd)":/backup \
alpine sh -c "rm -rf /data/* && tar xzf /backup/nimblebrain-backup.tar.gz -C /data"
docker compose up -d

The ghcr.io/nimblebraininc/nimblebrain-runtime image runs the platform on Bun. Connectors are remote MCP servers, so nothing an operator installs runs in this container and the image carries no runtime for hosting one:

Component Version Purpose
Bun latest JavaScript/TypeScript runtime (runs the platform itself)
Python 3.13 Base image
Node.js + npm 24 Builds the in-image platform app UIs
Git, curl, unzip System Install steps and the health check

The container runs as a non-root user (UID 1000). The entrypoint is bun run src/cli/index.ts serve, which starts the HTTP API server on port 27247 with NB_WORK_DIR=/data and NB_HOST=0.0.0.0.

The web container’s Caddy server already handles reverse proxying for the default setup. If you want to place your own reverse proxy in front of the stack (for TLS termination, custom domain, etc.), point it at port 27246:

nginx.conf
server {
listen 443 ssl;
server_name nb.example.com;
ssl_certificate /etc/ssl/certs/nb.example.com.pem;
ssl_certificate_key /etc/ssl/private/nb.example.com-key.pem;
location / {
proxy_pass http://127.0.0.1:27246;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off;
}
}

Check the logs:

Terminal window
docker compose logs platform

Common causes:

  • ANTHROPIC_API_KEY is missing or invalid
  • A missing or malformed instance.json — the server refuses to start without a valid auth adapter config. Check the logs for the validation error and see Security.

The web container waits for the platform health check to pass. If the platform is unhealthy, the web container stays in a “waiting” state. Fix the platform first.

Change the host port mapping in docker-compose.yml:

ports:
- "9090:8080" # Access the UI on port 9090 instead