# Running Moltis in Docker Moltis is available as a multi-architecture Docker image supporting both `linux/amd64` and `linux/arm64`. The image is published to GitHub Container Registry on every release. ## Quick Start ```bash docker run -d \ --name moltis \ -p 13131:13131 \ -p 13132:13132 \ -p 1455:1455 \ -v moltis-config:/home/moltis/.config/moltis \ -v moltis-data:/home/moltis/.moltis \ -v /var/run/docker.sock:/var/run/docker.sock \ ghcr.io/moltis-org/moltis:latest ``` Open https://localhost:13131 in your browser and configure your LLM provider to start chatting. For unattended bootstraps, add `MOLTIS_TOKEN`, `MOLTIS_PROVIDER`, and `MOLTIS_API_KEY` before first start. That pre-configures auth plus one LLM provider so you can skip the browser setup wizard entirely. ### Ports | Port | Purpose | |------|---------| | 13131 | Gateway (HTTPS by default, HTTP with `--no-tls`) — web UI, API, WebSocket | | 13132 | HTTP — CA certificate download for local TLS trust | | 1455 | OAuth callback — required for OpenAI Codex and other providers with pre-registered redirect URIs | ### Trusting the TLS certificate Moltis generates a self-signed CA on first run. Browsers will show a security warning until you trust this CA. Port 13132 serves the certificate over plain HTTP so you can download it: ```bash # Download the CA certificate curl -o moltis-ca.pem http://localhost:13132/certs/ca.pem # macOS — add to system Keychain and trust it sudo security add-trusted-cert -d -r trustRoot \ -k /Library/Keychains/System.keychain moltis-ca.pem # Linux (Debian/Ubuntu) sudo cp moltis-ca.pem /usr/local/share/ca-certificates/moltis-ca.crt sudo update-ca-certificates ``` After trusting the CA, restart your browser. The warning will not appear again (the CA persists in the mounted config volume). This local CA only solves certificate trust for names included in the generated server certificate, such as `localhost`, `moltis.localhost`, the container or host name, and sometimes an inferred non-loopback bind IP. It does not make a certificate valid for an arbitrary public VPS IP address or hosting-provider domain. Browsers still reject those targets with a certificate name mismatch if they are not present in the certificate SAN list. IP-address URLs require an IP SAN. For direct `https://:13131` access, set `tls.public_ip` to that address before starting Moltis so the auto-generated certificate includes it: ```toml [tls] public_ip = "203.0.113.10" ``` Regular public TLS deployments should use a domain name. For internet-facing Docker deployments, prefer a domain name plus a reverse proxy with public CA certificates. Run Moltis with `--no-tls`, set `MOLTIS_BEHIND_PROXY=true`, and point the proxy at `http://:13131`. If you want Moltis to serve HTTPS directly, mount a certificate and private key whose SANs cover the public hostname and set `tls.cert_path` and `tls.key_path` in `moltis.toml`. ```admonish note When accessing from localhost, no authentication is required. If you access Moltis from a different machine (e.g., over the network), a setup code is printed to the container logs for authentication setup. Docker's default bridge networking rewrites the request's apparent source address, so this "no auth needed" convenience does not apply automatically inside a container even when the browser genuinely is on the same machine — see [Docker and check 4](authentication.md#docker-and-check-4) for why, and the `MOLTIS_TRUST_DOCKER_LOOPBACK` opt-in if you've confirmed your own port publish is loopback-only. ~~~bash docker logs moltis ~~~ ``` ## Volume Mounts Moltis uses two directories that should be persisted: | Path | Contents | |------|----------| | `/home/moltis/.config/moltis` | Configuration files: `moltis.toml`, `credentials.json`, `mcp-servers.json` | | `/home/moltis/.moltis` | Runtime data: databases, sessions, memory files, logs | | `/home/moltis/.npm` | npm cache (used by stdio-based MCP servers) | You can use named volumes (as shown above) or bind mounts to local directories for easier access to configuration files: ```bash docker run -d \ --name moltis \ -p 13131:13131 \ -p 13132:13132 \ -p 1455:1455 \ -v ./config:/home/moltis/.config/moltis \ -v ./data:/home/moltis/.moltis \ -v /var/run/docker.sock:/var/run/docker.sock \ ghcr.io/moltis-org/moltis:latest ``` With bind mounts, you can edit `config/moltis.toml` directly on the host. ```admonish warning On a fresh checkout, Docker auto-creates `./config` and `./data` as `root:root` the first time it bind-mounts them. Moltis runs as a non-root user inside the container, so the very first start can fail with `unable to open database file` before that user can write to them. Fix it once, before starting the container: ~~~bash mkdir -p ./config ./data docker run --rm --user root --entrypoint chown \ -v ./config:/home/moltis/.config/moltis \ -v ./data:/home/moltis/.moltis \ ghcr.io/moltis-org/moltis:latest \ -R moltis:moltis /home/moltis/.config /home/moltis/.moltis ~~~ Using the image's own `moltis` username rather than a hardcoded UID keeps this working even if the image's UID assignment ever changes. Named volumes (the Quick Start example above) don't need this — Docker leaves an existing named volume's ownership alone, only bind-mount source directories get created as `root:root` on first use. ``` ## Docker Socket (Sandbox Execution) Moltis runs LLM-generated shell commands inside isolated containers for security. When Moltis itself runs in a container, it needs access to the host's container runtime to create these sandbox containers. ```bash # Recommended for full container isolation -v /var/run/docker.sock:/var/run/docker.sock ``` **Without the socket mount**, Moltis automatically falls back to the [restricted-host sandbox](sandbox.md#restricted-host-sandbox), which provides lightweight isolation by clearing environment variables, restricting `PATH`, and applying resource limits via `ulimit`. Commands will execute successfully inside the Moltis container but without filesystem or network isolation. For full container-level isolation (filesystem boundaries, network policies), mount the Docker socket. If Moltis is itself running in Docker and your `data_dir()` mount is backed by a different host path than `/home/moltis/.moltis`, Moltis tries to discover that host path automatically from `docker inspect`/`podman inspect`. It first checks the current container's hostname/cgroup references, then scans running containers for an unambiguous mount of Moltis' data directory. If that lookup still fails, add this to `/home/moltis/.config/moltis/moltis.toml` inside the container: ```toml [tools.exec.sandbox] host_data_dir = "/absolute/host/path/to/data" ``` For a bind mount like `-v ./data:/home/moltis/.moltis`, use the resolved host path to `./data`. This setting is also used by sandboxed browser containers for their persistent Chrome profile directory. If browser startup logs show `/data/browser-profile/SingletonLock: Permission denied`, Moltis probably fell back to the in-container path (`/home/moltis/.moltis/...`) instead of the real host path. Set `host_data_dir` to the host-visible data directory and restart Moltis so new sandbox and browser containers pick up the corrected mount source. ### Security Consideration Mounting the Docker socket gives the container full access to the Docker daemon. This is equivalent to root access on the host for practical purposes. Only run Moltis containers from trusted sources (official images from `ghcr.io/moltis-org/moltis`). ## Docker Compose See [`examples/docker-compose.yml`](https://github.com/moltis-org/moltis/blob/main/examples/docker-compose.yml) for a complete example: ```yaml services: moltis: image: ghcr.io/moltis-org/moltis:latest container_name: moltis restart: unless-stopped ports: - "13131:13131" - "13132:13132" - "1455:1455" # OAuth callback (OpenAI Codex, etc.) volumes: - ./config:/home/moltis/.config/moltis - ./data:/home/moltis/.moltis - /var/run/docker.sock:/var/run/docker.sock ``` For unattended recovery after host reboots or in-place `/update`, store the vault recovery key as a Docker secret and point Moltis at the mounted file: ```yaml services: moltis: image: ghcr.io/moltis-org/moltis:latest environment: MOLTIS_VAULT_AUTO_UNSEAL_KEY_FILE: /run/secrets/moltis_vault_recovery_key secrets: - moltis_vault_recovery_key secrets: moltis_vault_recovery_key: file: ./moltis-vault-recovery-key ``` This lets encrypted environment variables and channel credentials load during startup. Treat the secret file as sensitive as the vault recovery key itself. If you create the secret file before the vault is initialized, Docker will accept the mount but Moltis cannot auto-unseal from an empty file. After you initialize the vault in **Settings > Encryption**, copy the one-time recovery key into this file before relying on unattended auto-unseal. ### Coolify (Hetzner/VPS) For Coolify service stacks, use [`examples/docker-compose.coolify.yml`](https://github.com/moltis-org/moltis/blob/main/examples/docker-compose.coolify.yml). It is preconfigured for reverse-proxy deployments (`--no-tls`) and includes the Docker socket mount for sandboxed command execution. Key points: - Set `MOLTIS_TOKEN` in the Coolify UI before first deploy. - Set `SERVICE_FQDN_MOLTIS_13131` to your app domain. - Keep Moltis in `--no-tls` mode behind Coolify's reverse proxy. If requests are redirected to `:13131`, check that TLS is disabled in Moltis. - Keep `/var/run/docker.sock:/var/run/docker.sock` mounted if you want sandbox isolation for exec tools. Start with: ```bash docker compose up -d docker compose logs -f moltis # watch for startup messages ``` ## Browser Sandbox in Docker When Moltis runs inside Docker and launches a sandboxed browser, the browser container is a sibling container on the host. By default, Moltis connects to `127.0.0.1` which only reaches its own loopback, not the browser. The sibling browser also needs a host-visible mount for its Chrome profile. If your Moltis data directory is bind-mounted or stored somewhere that is not visible on the host as `/home/moltis/.moltis`, configure `[tools.exec.sandbox].host_data_dir` as described in [Docker Socket Sandbox Execution](#docker-socket-sandbox-execution). Without that override, Chrome may fail with `SingletonLock: Permission denied` when the browser container tries to write `/data/browser-profile`. Add `container_host` to your `moltis.toml` so Moltis can reach the browser container through the host's port mapping: ```toml [tools.browser] container_host = "host.docker.internal" ``` On Linux, add `--add-host` to the Moltis container so `host.docker.internal` resolves to the host: ```bash docker run -d \ --name moltis \ --add-host=host.docker.internal:host-gateway \ -p 13131:13131 \ -p 13132:13132 \ -p 1455:1455 \ -v moltis-config:/home/moltis/.config/moltis \ -v moltis-data:/home/moltis/.moltis \ -v /var/run/docker.sock:/var/run/docker.sock \ ghcr.io/moltis-org/moltis:latest ``` Alternatively, use the Docker bridge gateway IP directly (`container_host = "172.17.0.1"` on most Linux setups). ## Podman Support Moltis works with Podman using its Docker-compatible API. Mount the Podman socket instead of the Docker socket: ```bash # Podman rootless podman run -d \ --name moltis \ -p 13131:13131 \ -p 13132:13132 \ -p 1455:1455 \ -v moltis-config:/home/moltis/.config/moltis \ -v moltis-data:/home/moltis/.moltis \ -v /run/user/$(id -u)/podman/podman.sock:/var/run/docker.sock \ ghcr.io/moltis-org/moltis:latest # Podman rootful podman run -d \ --name moltis \ -p 13131:13131 \ -p 13132:13132 \ -p 1455:1455 \ -v moltis-config:/home/moltis/.config/moltis \ -v moltis-data:/home/moltis/.moltis \ -v /run/podman/podman.sock:/var/run/docker.sock \ ghcr.io/moltis-org/moltis:latest ``` You may need to enable the Podman socket service first: ```bash # Rootless systemctl --user enable --now podman.socket # Rootful sudo systemctl enable --now podman.socket ``` ## Environment Variables | Variable | Description | |----------|-------------| | `MOLTIS_CONFIG_DIR` | Override config directory (default: `~/.config/moltis`) | | `MOLTIS_DATA_DIR` | Override data directory (default: `~/.moltis`) | | `MOLTIS_NO_TLS` | Disable TLS (serve plain HTTP) — equivalent to `--no-tls` | Example: ```bash docker run -d \ --name moltis \ -p 13131:13131 \ -p 13132:13132 \ -p 1455:1455 \ -e MOLTIS_CONFIG_DIR=/config \ -e MOLTIS_DATA_DIR=/data \ -v ./config:/config \ -v ./data:/data \ -v /var/run/docker.sock:/var/run/docker.sock \ ghcr.io/moltis-org/moltis:latest ``` ### API Keys and the `[env]` Section Features like web search (Brave), embeddings, and LLM provider API calls read keys from process environment variables (`std::env::var`). In Docker, there are three ways to provide these: **Option 1: Generic first-run LLM bootstrap** (best for one provider) Use this when you want a minimal `docker compose` file with one chat provider and no manual setup: ```yaml services: moltis: image: ghcr.io/moltis-org/moltis:latest environment: MOLTIS_TOKEN: "change-me" MOLTIS_PROVIDER: "openai" MOLTIS_API_KEY: "sk-..." ``` `MOLTIS_PROVIDER` must be a Moltis provider name such as `openai`, `anthropic`, `gemini`, `groq`, `openrouter`, or `mistral`. The shorter aliases `PROVIDER` and `API_KEY` also work, but the `MOLTIS_*` names are preferred because they are less likely to collide with other containers. **Option 2: Provider-specific `docker -e` flags** (takes precedence for that provider) ```bash docker run -d \ --name moltis \ -e BRAVE_API_KEY=your-key \ -e OPENROUTER_API_KEY=sk-or-... \ ... ghcr.io/moltis-org/moltis:latest ``` **Option 3: `[env]` section in `moltis.toml`** Add an `[env]` section to your config file. These variables are injected into the Moltis process at startup, making them available to all features: ```toml [env] BRAVE_API_KEY = "your-brave-key" OPENROUTER_API_KEY = "sk-or-..." ``` If a variable is set both via `docker -e` and `[env]`, the Docker/host environment value wins — `[env]` never overwrites existing variables. ```admonish info title="Settings UI env vars" Environment variables set through the Settings UI (Settings > Environment) are stored in SQLite. At startup, Moltis injects them into the process environment so they are available to all features (search, embeddings, provider API calls), not just sandbox commands. Precedence order (highest wins): 1. Host / `docker -e` environment variables 2. Config file `[env]` section 3. Settings UI environment variables ``` ## Building Locally To build the Docker image from source: ```bash # Single architecture (current platform) docker build -t moltis:local . # Multi-architecture (requires buildx) docker buildx build --platform linux/amd64,linux/arm64 -t moltis:local . ``` ## OrbStack OrbStack on macOS works identically to Docker — use the same socket path (`/var/run/docker.sock`). OrbStack's lightweight Linux VM provides good isolation with lower resource usage than Docker Desktop. ## Troubleshooting ### "Cannot connect to Docker daemon" The Docker socket is not mounted or the Moltis user doesn't have permission to access it. Verify: ```bash docker exec moltis ls -la /var/run/docker.sock ``` ### Setup code not appearing in logs (for network access) The setup code only appears when accessing from a non-localhost address. If you're accessing from the same machine via `localhost`, no setup code is needed. For network access, wait a few seconds for the gateway to start, then check logs: ```bash docker logs moltis 2>&1 | grep -i setup ``` ### OAuth authentication error (OpenAI Codex) If clicking **Connect** for OpenAI Codex shows "unknown_error" on OpenAI's page, port 1455 is not reachable from your browser. Make sure you published it: ```bash -p 1455:1455 ``` If you're running Moltis on a remote server (cloud VM, VPS) and accessing it over the network, `localhost:1455` on the browser side points to your local machine — not the server. In that case, authenticate via the CLI instead: ```bash docker exec -it moltis moltis auth login --provider openai-codex ``` The CLI opens a browser on the machine where you run the command and handles the OAuth callback locally. If automatic callback capture fails, Moltis prompts you to paste the callback URL (or `code#state`) into the terminal. Tokens are saved to the config volume and picked up by the running gateway automatically. ### Permission denied on bind mounts When using bind mounts, ensure the directories exist and are writable: ```bash mkdir -p ./config ./data chmod 755 ./config ./data ``` The container runs as user `moltis` (UID 1000). If you see permission errors, you may need to adjust ownership: ```bash sudo chown -R 1000:1000 ./config ./data ```