Skip to main content

Docker (optional)

Docker is optional. Use it only if you want a containerized gateway or to validate the Docker flow.

Is Docker right for me?

  • Yes: you want an isolated, throwaway gateway environment or to run Fased on a host without local installs.
  • No: you’re running on your own machine and just want the fastest dev loop. Use the normal install flow instead.
  • Sandboxing note: agent sandboxing uses Docker too, but it does not require the full gateway to run in Docker. See Sandboxing.
This guide covers:
  • Containerized Gateway (full Fased in Docker)
  • Per-session Agent Sandbox (host gateway + Docker-isolated agent tools)
Sandboxing details: Sandboxing

Requirements

  • Docker Desktop (or Docker Engine) + Docker Compose v2
  • At least 2 GB RAM for image build (pnpm install may be OOM-killed on 1 GB hosts with exit 137)
  • Enough disk for images + logs

Containerized Gateway (Docker Compose)

Clone the repo, then run from repo root:
git clone https://github.com/fased-ai/fased.git fased
cd fased
./docker-setup.sh
This script:
  • builds the gateway image
  • runs CLI onboarding
  • prints dashboard, token, and pairing hints
  • starts the gateway via Docker Compose
  • generates a gateway token and writes it to .env
Optional env vars:
  • FASED_DOCKER_APT_PACKAGES — install extra apt packages during build
  • FASED_EXTRA_MOUNTS — add extra host bind mounts
  • FASED_HOME_VOLUME — persist /home/node in a named volume
After it finishes:
  • Open http://localhost:18789/ in your browser.
  • Paste the token from .env or the dashboard link if the browser asks for one.
  • In the UI, finish setup from the selected Agent: Models first, then Chat.
  • Need the URL again? Run docker compose run --rm fased-cli dashboard --no-open.
It writes config/workspace on the host:
  • ~/.fased/
  • ~/.fased/workspace
Running on a VPS? See Hetzner (Docker VPS).

Manual flow (compose)

docker build -t fased:local -f Dockerfile .
docker compose run --rm fased-cli onboard
docker compose up -d fased-gateway
Note: run docker compose ... from the repo root. If you enabled FASED_EXTRA_MOUNTS or FASED_HOME_VOLUME, the setup script writes docker-compose.extra.yml; include it when running Compose elsewhere:
docker compose -f docker-compose.yml -f docker-compose.extra.yml <command>

Control UI token + pairing (Docker)

If you see “unauthorized” or “disconnected (1008): pairing required”, fetch a fresh dashboard link and approve the browser device:
docker compose run --rm fased-cli dashboard --no-open
docker compose run --rm fased-cli devices list
docker compose run --rm fased-cli devices approve <requestId>
More detail: Dashboard, Devices.

Setup after Docker starts

Use the browser UI for normal setup:
  1. open http://localhost:18789/
  2. choose the default Agent, shown as Assistant
  3. configure model auth in Agent > Models
  4. test one message in Chat
  5. add channels in Agent > Channels
  6. add API connectors in Agent > Services
The CLI onboarding command is still useful for automation, repair, or scripted container setup, but normal users should not need to edit JSON by hand.

Extra mounts (optional)

If you want to mount additional host directories into the containers, set FASED_EXTRA_MOUNTS before running docker-setup.sh. This accepts a comma-separated list of Docker bind mounts and applies them to both fased-gateway and fased-cli by generating docker-compose.extra.yml. Example:
export FASED_EXTRA_MOUNTS="$HOME/.codex:/home/node/.codex:ro,$HOME/github:/home/node/github:rw"
./docker-setup.sh
Notes:
  • Paths must be shared with Docker Desktop on macOS/Windows.
  • Each entry must be source:target[:options] with no spaces, tabs, or newlines.
  • If you edit FASED_EXTRA_MOUNTS, rerun docker-setup.sh to regenerate the extra compose file.
  • docker-compose.extra.yml is generated. Don’t hand-edit it.

Persist the entire container home (optional)

If you want /home/node to persist across container recreation, set a named volume via FASED_HOME_VOLUME. This creates a Docker volume and mounts it at /home/node, while keeping the standard config/workspace bind mounts. Use a named volume here (not a bind path); for bind mounts, use FASED_EXTRA_MOUNTS. Example:
export FASED_HOME_VOLUME="fased_home"
./docker-setup.sh
You can combine this with extra mounts:
export FASED_HOME_VOLUME="fased_home"
export FASED_EXTRA_MOUNTS="$HOME/.codex:/home/node/.codex:ro,$HOME/github:/home/node/github:rw"
./docker-setup.sh
Notes:
  • Named volumes must match ^[A-Za-z0-9][A-Za-z0-9_.-]*$.
  • If you change FASED_HOME_VOLUME, rerun docker-setup.sh to regenerate the extra compose file.
  • The named volume persists until removed with docker volume rm <name>.

Install extra apt packages (optional)

If you need system packages inside the image (for example, build tools or media libraries), set FASED_DOCKER_APT_PACKAGES before running docker-setup.sh. This installs the packages during the image build, so they persist even if the container is deleted. Example:
export FASED_DOCKER_APT_PACKAGES="ffmpeg build-essential"
./docker-setup.sh
Notes:
  • This accepts a space-separated list of apt package names.
  • If you change FASED_DOCKER_APT_PACKAGES, rerun docker-setup.sh to rebuild the image.
The default Docker image is minimal and runs as the non-root node user. This keeps the attack surface small, but it means:
  • no system package installs at runtime
  • no Homebrew by default
  • no bundled Chromium/Playwright browsers
If you want a more full-featured container, use these opt-in knobs:
  1. Persist /home/node so browser downloads and tool caches survive:
export FASED_HOME_VOLUME="fased_home"
./docker-setup.sh
  1. Bake system deps into the image (repeatable + persistent):
export FASED_DOCKER_APT_PACKAGES="git curl jq"
./docker-setup.sh
  1. Install Playwright browsers without npx (avoids npm override conflicts):
docker compose run --rm fased-cli \
  node /app/node_modules/playwright-core/cli.js install chromium
If you need Playwright to install system deps, rebuild the image with FASED_DOCKER_APT_PACKAGES instead of using --with-deps at runtime.
  1. Persist Playwright browser downloads:
  • Set PLAYWRIGHT_BROWSERS_PATH=/home/node/.cache/ms-playwright in docker-compose.yml.
  • Ensure /home/node persists via FASED_HOME_VOLUME, or mount /home/node/.cache/ms-playwright via FASED_EXTRA_MOUNTS.

Permissions + EACCES

The image runs as node (uid 1000). If you see permission errors on /home/node/.fased, make sure your host bind mounts are owned by uid 1000. Example (Linux host):
sudo chown -R 1000:1000 /path/to/fased-config /path/to/fased-workspace
If you choose to run as root for convenience, you accept the security tradeoff. To speed up rebuilds, order your Dockerfile so dependency layers are cached. This avoids re-running pnpm install unless lockfiles change:
FROM node:22-bookworm

# Install Bun (required for build scripts)
RUN curl -fsSL https://bun.sh/install | bash
ENV PATH="/root/.bun/bin:${PATH}"

RUN corepack enable

WORKDIR /app

# Cache dependencies unless package metadata changes
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY ui/package.json ./ui/package.json
COPY scripts ./scripts

RUN pnpm install --frozen-lockfile

COPY . .
RUN pnpm build
RUN pnpm ui:install
RUN pnpm ui:build

ENV NODE_ENV=production

CMD ["node","dist/index.js"]

Channel setup (optional)

Use Agent > Channels in the Control UI for normal setup. It mirrors the current channel onboarding flow and keeps account credentials separate from Agent routing. For scripted Docker setups, you can still run channel CLI commands through the CLI container, then restart the gateway if that command changed runtime config. Docs: WhatsApp, Telegram, Discord

OpenAI Codex OAuth (headless Docker)

If you pick OpenAI Codex OAuth from Agent > Models, it opens a browser URL and tries to capture a callback on http://127.0.0.1:1455/auth/callback. In Docker or headless setups that callback can show a browser error. Copy the full redirect URL you land on and paste it back into the auth prompt to finish auth.

Health check

docker compose exec fased-gateway node dist/index.js health
The Compose service already receives FASED_GATEWAY_TOKEN from .env; the health command reads runtime config/env and does not take a --token flag.

E2E smoke test (Docker)

scripts/e2e/onboard-docker.sh

QR import smoke test (Docker)

pnpm test:docker:qr

Notes

  • Gateway bind defaults to lan for container use.
  • Dockerfile CMD uses --allow-unconfigured; mounted config with gateway.mode not local will still start. Override CMD to enforce the guard.
  • The gateway container is the source of truth for sessions (~/.fased/agents/<agentId>/sessions/).

Agent Sandbox (host gateway + Docker tools)

Deep dive: Sandboxing This is separate from running the whole Gateway in Docker. The Gateway can run on the host while selected tool sessions run inside Docker containers.
{
  agents: {
    defaults: {
      sandbox: {
        mode: "non-main", // off | non-main | all
        scope: "agent", // session | agent | shared
        workspaceAccess: "none", // none | ro | rw
        workspaceRoot: "~/.fased/sandboxes",
        docker: {
          image: "fased-sandbox:bookworm-slim",
          workdir: "/workspace",
          readOnlyRoot: true,
          tmpfs: ["/tmp", "/var/tmp", "/run"],
          network: "none",
          user: "1000:1000",
          capDrop: ["ALL"],
          env: { LANG: "C.UTF-8" },
          setupCommand: "apt-get update && apt-get install -y git curl jq",
          pidsLimit: 256,
          memory: "1g",
          memorySwap: "2g",
          cpus: 1,
          ulimits: {
            nofile: { soft: 1024, hard: 2048 },
            nproc: 256,
          },
          seccompProfile: "/path/to/seccomp.json",
          apparmorProfile: "fased-sandbox",
          dns: ["1.1.1.1", "8.8.8.8"],
          extraHosts: ["internal.service:10.0.0.5"],
        },
        prune: {
          idleHours: 24, // 0 disables idle pruning
          maxAgeDays: 7, // 0 disables max-age pruning
        },
      },
    },
  },
  tools: {
    sandbox: {
      tools: {
        allow: [
          "exec",
          "process",
          "read",
          "write",
          "edit",
          "sessions_list",
          "sessions_history",
          "sessions_send",
          "sessions_spawn",
          "session_status",
        ],
        deny: ["browser", "canvas", "nodes", "cron", "discord", "gateway"],
      },
    },
  },
}
Key defaults:
  • one sandbox per agent by default
  • sandbox workspace under ~/.fased/sandboxes
  • Docker network disabled by default
  • host browser/camera/canvas are blocked by default
  • deny tool policy wins over allow
  • scope: "shared" disables cross-session isolation
For full precedence, browser sandboxing, per-agent overrides, and hardening knobs, use Sandboxing and Multi-Agent Sandbox & Tools.

Build the default sandbox image

scripts/sandbox-setup.sh
This builds fased-sandbox:bookworm-slim using deploy/containers/Dockerfile.sandbox.

Optional sandbox images

If you want a sandbox image with common build tooling (Node, Go, Rust, etc.), build the common image:
scripts/sandbox-common-setup.sh
This builds fased-sandbox-common:bookworm-slim. To use it:
{
  agents: {
    defaults: {
      sandbox: { docker: { image: "fased-sandbox-common:bookworm-slim" } },
    },
  },
}
To run the browser tool inside the sandbox, build the browser image:
scripts/sandbox-browser-setup.sh

Custom sandbox image

Build your own image and point config to it:
docker build -t my-fased-sbx -f deploy/containers/Dockerfile.sandbox .
{
  agents: {
    defaults: {
      sandbox: { docker: { image: "my-fased-sbx" } },
    },
  },
}

Isolation notes

  • Hard wall only applies to tools (exec/read/write/edit/apply_patch).
  • Host-only tools like browser/camera/canvas are blocked by default.
  • Allowing browser in sandbox breaks isolation (browser runs on host).

Troubleshooting

  • Image missing: build with scripts/sandbox-setup.sh or set agents.defaults.sandbox.docker.image.
  • Container not running: it will auto-create per session on demand.
  • Permission errors in sandbox: set docker.user to a UID:GID that matches your mounted workspace ownership (or chown the workspace folder).
  • Custom tools not found: Fased runs commands with sh -lc (login shell), which sources /etc/profile and may reset PATH. Set docker.env.PATH to prepend your custom tool paths (e.g., /custom/bin:/usr/local/share/npm-global/bin), or add a script under /etc/profile.d/ in your Dockerfile.