Skip to main content
This guide sets up Comis in a Docker container. You do not need Node.js installed on your system — Docker handles everything.
You don’t need to understand the technical details to use this feature. The configuration examples below are copy-paste ready.
You need Docker Engine 24+ and Docker Compose v2+ installed. Check with:
docker --version
docker compose version
Production = Linux host only. macOS and Windows Docker Desktop run containers inside a linuxkit VM whose kernel does not allow nested PID-namespace + procfs mounts. This breaks the bubblewrap exec sandbox.On Docker Desktop the daemon detects this at startup and automatically disables the exec sandbox so the agent is functional for development. The trade-off:
  • Dev/testing UX on Mac and Windows: the agent runs end-to-end. You can drive exec commands, run scripts in the workspace, and exercise every feature that depends on shell execution.
  • Security guarantees inside the container are dropped on Docker Desktop. With the sandbox off, agent-issued shell commands can read /home/comis/.comis/.env, the encrypted secrets.db, /etc/comis/config.yaml, channel tokens, the SECRETS_MASTER_KEY, and anything else the daemon user owns. A single prompt injection exfiltrates everything.
This is fine for development and local testing only. For production, deploy to a real Linux host (bare metal, VPS, or any Linux cloud VM) where the kernel actually allows the sandbox to run — the same image and compose files Just Work, you don’t need a code change.See Operations: Docker → Platform Support for the full explanation, including local Linux-VM alternatives (Lima / Colima vz driver / OrbStack / UTM) that get you a working sandbox on a Mac.

Automated setup

The repository includes a setup script that creates directories, generates a gateway token, builds the Docker image, and starts the daemon. This is the recommended way to get started.
1

Clone the repository

git clone https://github.com/comisai/comis.git
cd comis
2

Configure environment

Copy the included environment template and add your API keys:
cp .env.docker.example .env
Open .env in your editor and fill in at minimum:
ANTHROPIC_API_KEY=your-key-here
# or
OPENAI_API_KEY=your-key-here
Never store API keys, tokens, or passwords directly in config.yaml. Use the .env file or Secret Manager for credential management.
The .env.docker.example file lists all available settings including channel tokens (Telegram, Discord, Slack), network binding options, and optional provider keys. See the comments in that file for details.
3

Run the setup script

./docker-setup.sh
The script performs these steps automatically:
  1. Creates the data directory (~/.comis) with a traces/ subdirectory
  2. Generates a gateway authentication token (or reuses an existing one)
  3. Writes token and network settings to ~/.comis/.env
  4. Creates a default config.yaml with gateway, logging, and memory paths
  5. Builds the Docker image from the included Dockerfile
  6. Fixes data directory ownership for the container’s non-root user (UID 1000)
  7. Starts the comis-daemon service and waits for its health check to pass
When complete, the script prints the gateway URL and a truncated token.
4

Verify

Confirm the daemon is healthy:
curl http://localhost:4766/health
Expected response:
{ "status": "ok" }

Browser tool (optional)

The published image is browser-free. To use the agent browser tool, rebuild from the repo with one of three build args mirroring the bare-VPS install flags. Image-size impact in parentheses.
# Stock Chrome (+400 MB)
docker build \
  --build-arg COMIS_WITH_BROWSER=1 \
  -t comisai/comis:browser .

# Headed via Xvfb — for sites that detect headless mode (+15 MB on top)
docker build \
  --build-arg COMIS_WITH_XVFB=1 \
  -t comisai/comis:xvfb .

# Stealth Chromium (CloakBrowser, +500 MB) — bypasses Cloudflare Turnstile,
# FingerprintJS, BrowserScan, and Reddit's secondary fingerprint check
docker build \
  --build-arg COMIS_WITH_CLOAKBROWSER=1 \
  -t comisai/comis:cloak .

# Full stack
docker build \
  --build-arg COMIS_WITH_CLOAKBROWSER=1 \
  --build-arg COMIS_WITH_XVFB=1 \
  -t comisai/comis:cloak-xvfb .
The entrypoint shim (/usr/local/bin/comis-entrypoint.sh) starts Xvfb on :99 automatically when the image was built with COMIS_WITH_XVFB=1. The daemon’s findChrome() probes ~/.cloakbrowser/chromium-*/chrome first, so when the cloak binary is baked in it wins over stock Chrome with no config change. Run the stealth image identically to the no-browser one — same volumes, same ports, same env vars:
docker run -d \
  --name comis \
  --restart unless-stopped \
  -p 127.0.0.1:4766:4766 \
  -v comis-data:/home/comis/.comis \
  -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
  comisai/comis:cloak

Alternative: installer-based image

The repo also ships Dockerfile.install — a fresh Ubuntu 24.04 base that runs install.sh end-to-end inside the container. Same build args, same end state, but exercises the installer code path verbatim. Useful for CI testing or when you want strict parity with a bare-VPS deploy.
docker build -f Dockerfile.install \
  --build-arg COMIS_WITH_CLOAKBROWSER=1 \
  -t comis-installed:cloak .
The main Dockerfile is the production path (multi-stage, smaller, faster). Dockerfile.install is the validation/parity path.
Datacenter IPs (AWS, DigitalOcean, Hetzner, Hostinger, …) are pre-blocked by Reddit, X/Twitter, LinkedIn, and many anti-bot services at the network layer — independent of browser fingerprint. The browser flags fix fingerprint detection, not IP reputation. If your container runs on a datacenter ASN and you need Reddit/social scraping, pair --with-cloakbrowser with a residential proxy.CloakBrowser’s binary is free for self-hosted use; bundling into a service distributed to third-party customers requires an OEM license from CloakHQ. See the binary license.
See agent-tools/browser for the full flag matrix, verified detection-evasion outcomes, and security model.

Optional services

The daemon is the only service that starts by default. The web dashboard and CLI are available as Docker Compose profiles.

Web dashboard

Start the Nginx-served web UI on port 8080:
docker compose --profile web up -d
Then open http://localhost:8080 in your browser. The dashboard proxies API calls and WebSocket connections to the daemon automatically.

CLI

Run one-shot CLI commands against the running daemon:
docker compose --profile cli run --rm comis-cli status
The CLI container shares the daemon’s network namespace, so it reaches the gateway at 127.0.0.1:4766 without extra configuration.

Stopping and updating

ActionCommand
Stop daemondocker compose down
Stop web dashboarddocker compose --profile web down
Restart daemondocker compose restart comis-daemon
Updategit pull && docker compose up -d --build
View logsdocker compose logs -f comis-daemon

Deploying on a VPS

The local-machine examples above use -p 4766:4766 for convenience, which binds the gateway to 0.0.0.0:4766 on the host. On a VPS that’s the public internet — anyone on the network can reach the gateway. Pick one of the two safer postures below.
The wizard’s “LAN mode” sets gateway.host: 0.0.0.0 inside the container. That’s correct — the daemon must bind all interfaces so Docker port-forwarding can reach it. The exposure decision is made on the host side by the -p mapping (or Compose ports:). Confusing the two is the #1 way to accidentally expose a daemon publicly.
Bind the host port to loopback only and put nginx (or Caddy/Traefik) in front to terminate TLS:
docker run -d \
  --name comis \
  --restart unless-stopped \
  -p 127.0.0.1:4766:4766 \
  -v comis-data:/home/comis/.comis \
  -e COMIS_CONFIG_PATHS=/home/comis/.comis/config.yaml \
  comisai/comis:latest-slim
Or in docker-compose.yml:
ports:
  - "127.0.0.1:4766:4766"
Then point your reverse proxy at http://127.0.0.1:4766 (HTTP) and http://127.0.0.1:4766/ws (WebSocket). See Operations: Reverse Proxy for full nginx / Caddy snippets with WebSocket upgrade headers.

Acceptable: direct exposure + token auth + firewall

If you don’t want a reverse proxy, you can expose the gateway directly, but you must combine three controls:
  1. Token auth on the gateway. The init wizard enables this by default (Authentication method → Token). Verify your ~/.comis/config.yaml contains a gateway.tokens: section and ~/.comis/.env has a COMIS_GATEWAY_TOKEN.
  2. Firewall to trusted IPs only. On Ubuntu/Debian:
    sudo ufw allow from <your-trusted-ip> to any port 4766
    sudo ufw enable
    
  3. TLS. Without TLS, the bearer token crosses the wire in cleartext. Either run with gateway.tls.cert configured (see Configuration: TLS) or front it with a reverse proxy that does TLS for you (in which case use the recommended posture above instead).

comis status from your laptop against a remote VPS

The CLI’s comis status defaults to reading ~/.comis/config.yaml on the machine where it runs. Copying the VPS’s config to your laptop won’t work on its own — gateway.host: 0.0.0.0 is a bind address that the CLI remaps to local loopback, not the VPS’s public IP. Set COMIS_GATEWAY_URL (and the matching token) on your laptop instead:
export COMIS_GATEWAY_URL=wss://comis.example.com/ws
export COMIS_GATEWAY_TOKEN=<the-token-from-the-VPS>
comis status
Use wss:// (TLS) when the gateway is reachable over the public internet. Use ws:// only over a trusted network (VPN, SSH tunnel).

Common issues

Common causes:
  • Docker not running — start Docker Desktop or the Docker daemon
  • Port 4766 in use — stop the other process or set COMIS_GATEWAY_PORT in .env
  • Missing openssl — the script uses openssl rand -hex 32 for token generation; install it with your system package manager
Symptom: curl http://127.0.0.1:4766/health returns “connection refused” or “connection reset by peer” even though the container is reported healthy.Fix: Two distinct binds are involved — get them in the right place:
  1. Host-side bind (which interface the host listens on): controlled by the left side of -p / Compose ports: (e.g. 127.0.0.1:4766:4766 vs 0.0.0.0:4766:4766).
  2. Container-side bind (which interface the daemon listens on inside the container): controlled by COMIS_GATEWAY_HOST in .env. Must be 0.0.0.0 so Docker port-forwarding can reach the daemon. Setting it to 127.0.0.1 is what produces the “connection reset” symptom — Docker forwards into the container’s eth0 IP, where nothing is listening. Since 1.0.25 the image defaults to 0.0.0.0; on older images set it explicitly.
Other things to check:
  • The container is running and healthy: docker compose ps
  • No firewall is blocking the port
  • If you mount a config.yaml with gateway.host: 127.0.0.1, that overrides the env var (since 1.0.25). Set gateway.host: 0.0.0.0 in the mounted config or remove the entry.
Symptom: The container starts but stops within seconds.Fix: Check the logs for the error:
docker compose logs comis-daemon
Common causes:
  • Invalid config.yaml — check YAML syntax and required fields. The daemon writes a recovery snapshot at ~/.comis/config.last-good.yaml; run cp ~/.comis/config.last-good.yaml ~/.comis/config.yaml to restore the previous-known-good config, then docker compose restart.
  • Missing API key — verify the .env file has the correct variable name
  • Port already in use — another process is using port 4766 on the host
Symptom: Bringing up Compose without first running docker-setup.sh, the daemon fails to persist credentials and you see cannot create /home/comis/.comis/.env: Is a directory in the logs.Cause: Docker bind-mounts ${COMIS_ENV_FILE:-~/.comis/.env} into the container. If the host path doesn’t exist, Docker silently creates it as a directory instead of a file, then every write fails.Fix: create the file before docker compose up:
mkdir -p ~/.comis
touch ~/.comis/.env
chmod 600 ~/.comis/.env
If .env already exists as a directory, remove it first: rmdir ~/.comis/.env && touch ~/.comis/.env.The repo’s docker-setup.sh handles this automatically — this only affects manual docker compose up.

Next steps

Configuration Guide

Customize your agent settings, add channels, and configure advanced options.

Verify Installation

Run diagnostic commands to confirm everything is working.

Operations: Docker

Production-ready Docker deployment with multi-stage builds and security hardening.