> ## Documentation Index
> Fetch the complete documentation index at: https://comis-feature-matrix-channel.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Install with Docker

> Run Comis in a Docker container with Docker Compose

This guide sets up Comis in a Docker container. You do not need Node.js installed
on your system -- Docker handles everything.

<Info>
  You don't need to understand the technical details to use this feature. The configuration examples below are copy-paste ready.
</Info>

<Warning>
  You need **Docker Engine 24+** and **Docker Compose v2+** installed. Check with:

  ```bash theme={null}
  docker --version
  docker compose version
  ```
</Warning>

<Warning>
  **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](/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.
</Warning>

## 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.

<Steps>
  <Step title="Clone the repository">
    ```bash theme={null}
    git clone https://github.com/comisai/comis.git
    cd comis
    ```
  </Step>

  <Step title="Configure environment">
    Copy the included environment template and add your API keys:

    ```bash theme={null}
    cp .env.docker.example .env
    ```

    Open `.env` in your editor and fill in at minimum:

    ```bash theme={null}
    ANTHROPIC_API_KEY=your-key-here
    # or
    OPENAI_API_KEY=your-key-here
    ```

    <Warning>
      Never store API keys, tokens, or passwords directly in `config.yaml`. Use the `.env` file or [Secret Manager](/security/secrets) for credential management.
    </Warning>

    <Tip>
      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.
    </Tip>
  </Step>

  <Step title="Run the setup script">
    ```bash theme={null}
    ./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.
  </Step>

  <Step title="Verify">
    Confirm the daemon is healthy:

    ```bash theme={null}
    curl http://localhost:4766/health
    ```

    Expected response:

    ```json theme={null}
    { "status": "ok" }
    ```
  </Step>
</Steps>

## 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.

```bash theme={null}
# 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:

```bash theme={null}
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.

```bash theme={null}
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.

<Warning>
  **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](https://github.com/CloakHQ/CloakBrowser/blob/main/BINARY-LICENSE.md).
</Warning>

See [agent-tools/browser](/agent-tools/browser#stealth-mode-anti-bot-detection) 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:

```bash theme={null}
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:

```bash theme={null}
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

| Action             | Command                                    |
| ------------------ | ------------------------------------------ |
| Stop daemon        | `docker compose down`                      |
| Stop web dashboard | `docker compose --profile web down`        |
| Restart daemon     | `docker compose restart comis-daemon`      |
| Update             | `git pull && docker compose up -d --build` |
| View logs          | `docker 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.

<Warning>
  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.
</Warning>

### Recommended: loopback bind + reverse proxy

Bind the host port to loopback only and put nginx (or Caddy/Traefik) in front
to terminate TLS:

```bash theme={null}
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`:

```yaml theme={null}
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](/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:
   ```bash theme={null}
   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](/installation/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:

```bash theme={null}
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

<AccordionGroup>
  <Accordion title="Setup script fails">
    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
  </Accordion>

  <Accordion title="Cannot access dashboard from host">
    **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. The image defaults to `0.0.0.0`.

    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. Set `gateway.host: 0.0.0.0` in the mounted config or remove the entry.
  </Accordion>

  <Accordion title="Container exits immediately">
    **Symptom:** The container starts but stops within seconds.

    **Fix:** Check the logs for the error:

    ```bash theme={null}
    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
  </Accordion>

  <Accordion title="Daemon can't write to .env (`Is a directory` error)">
    **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`:

    ```bash theme={null}
    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`.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Configuration Guide" icon="gear" href="/installation/configuration">
    Customize your agent settings, add channels, and configure advanced options.
  </Card>

  <Card title="Verify Installation" icon="circle-check" href="/installation/verify">
    Run diagnostic commands to confirm everything is working.
  </Card>

  <Card title="Operations: Docker" icon="docker" href="/operations/docker">
    Production-ready Docker deployment with multi-stage builds and security hardening.
  </Card>
</CardGroup>
