Skip to main content

Local Docker (optional)

Docker is optional and the full Docker Gateway is supported only on a local computer. Fased does not currently support a Docker-hosted Gateway on a VPS or cloud server, and there is no install.sh --hosting-docker mode. For a maintained VPS deployment, use the non-Docker one-command Hosting installer from the provider root console. It verifies the tagged Hosting release before privileged Fased installation and manages the app account, Tailscale, firewall and SSH hardening, systemd service, updates, and rollback. The local Docker setup does not provide those hosting controls and must not be substituted for it.

Is Docker right for me?

  • Yes: you want an isolated Gateway on your own local computer without a native Fased installation.
  • No: you are running on your own machine and want the fastest dev loop. Use the normal install flow instead.
  • No for VPS/cloud hosting: use install.sh --hosting; full Docker hosting is not a supported deployment path.
  • 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) with a current Docker Compose v2. The supplied setup uses Compose health dependencies and docker compose up --wait.
  • Linux: run the commands from Bash.
  • macOS: run the commands from Terminal. Docker runs the Linux signer image on both Apple silicon and Intel Macs.
  • Windows: Windows 11, or Windows 10 version 2004/build 19041 or newer, with WSL2, Ubuntu, Docker Desktop’s WSL2 backend, and Ubuntu integration enabled. Run every Fased command below in the Ubuntu WSL2 shell, never PowerShell, Command Prompt, or Git Bash. Microsoft’s setup instructions are Install WSL and Use systemd in WSL.
  • At least 2 GB RAM when building the image from source. On 1 GB hosts, pnpm install may be OOM-killed with exit 137.
  • Enough disk for images + logs
On a current Windows installation, open Administrator PowerShell only for the one-time WSL installation:
Restart Windows if requested, open Ubuntu, finish the Linux username setup, then enable the Ubuntu distribution in Docker Desktop under Settings > Resources > WSL Integration. Current Ubuntu installations made by wsl --install use systemd. If an older distribution does not, update WSL to 0.67.6 or later, ensure the exact installed distribution shows version 2, add the following inside Ubuntu, and run wsl --shutdown once from PowerShell:
Return to the Ubuntu shell and verify docker version and docker compose version before continuing. These are Ubuntu/WSL Bash commands, not PowerShell commands:
uname -s must print Linux. Keep the later clone, docker-setup.sh, update, backup, and rollback commands in this same Ubuntu shell.

Containerized Gateway (Docker Compose)

The curl installers and Docker are separate installation paths: Open the latest stable entry on GitHub Releases, copy its version without the leading v, then clone and run that exact release. Keep the source tag and image tag identical:
The image is public and supports anonymous pulls for linux/amd64 and linux/arm64. Users do not need a Docker Hub account, GitHub account, package token, or docker login. Tagged images contain a production-stamped signer; its version, release commit, and signer build-input digest are exposed through signer health and must match the exact image selected during an update. For an immutable local deployment, download that release’s fased-container-vX.Y.Z.json plus attestation bundle, verify it with the exact tag and Docker release workflow constraints shown in Update local Docker, and use its multi-architecture manifest digest directly:
latest is available for convenience, but a version tag or digest is safer when reproducibility matters:
With a published FASED_IMAGE, the setup script:
  • pulls the selected multi-architecture image, which contains both Fased and the matching native fased-signerd
  • refuses to replace an existing signer through the fresh-install path; Docker updates must use the offline transaction later in this guide
  • creates the non-root signer service and waits for protocol-v2 health before any wallet operation
  • runs CLI onboarding
  • prints dashboard, token, and pairing hints
  • recreates and health-checks the Gateway
  • generates a gateway token and writes it to .env
  • records the selected image in .env
Optional env vars:
  • FASED_IMAGE — use a selected image instead of building fased:local
  • 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
Signer keys, durable limits, idempotency records, policies, and audit state live in the Compose-managed fased-signer-state volume. The policy-limited application socket and administrative control socket use separate fased-signer-app-run and fased-signer-control-run volumes. The always-running Gateway and routine fased-cli mount only the application volume. Do not run docker compose down -v unless you intentionally want to destroy signer state and its wallets.

Build locally from source (alternative)

Use the source-build path when auditing or modifying the Dockerfile, testing unreleased source, or adding build-time packages. Select a stable release for a normal local build:
Without FASED_IMAGE, docker-setup.sh builds fased:local, records that selection in .env, reproducibly cross-builds the native Go signer for the target image architecture, stamps it as a development build with the packaged Fased version, runs onboarding, and starts the local services. A local development identity is explicit and is not a substitute for the provenance of a published release image. An unauthorized or denied error while using the public-image path usually means the requested tag does not exist or Docker is reusing stale credentials.

Local security boundary

The supplied local Compose configuration:
  • publishes Gateway and bridge ports on 127.0.0.1 only
  • packages fased-signerd with the matching image and runs it as a separate, non-root fased-signerd service before Gateway or CLI wallet work
  • keeps the signer database and master key in a signer-only persistent volume
  • separates the policy-limited application socket from the administrative control socket; Gateway and routine fased-cli mount only the application socket
  • exposes control access only through explicit, one-shot fased-signer-admin and fased-signer-enroll profile services; the admin service has no Gateway config, workspace, application socket, signer state, or network access
  • requires a real protocol-v2 signer health response, not just a socket file
  • makes Gateway and CLI treat signer lifecycle as external so Node cannot start a second signer process inside either container
  • runs all three services as the non-root node user
  • drops all Linux capabilities and enables no-new-privileges
  • does not use host networking, privileged mode, or a container-engine socket
  • health-checks both the native signer and Gateway
  • stores the generated .env with user-only permissions
  • excludes local .env*, .fased, SSH/private keys, and common credential directories from the image build context
This is Local container isolation, not the Hosting custody boundary. The Gateway cannot mount signer state or the administrative control socket, but it can request operations allowed by wallet policy through the application socket. The local account and Docker daemon still control all containers and volumes, so container separation does not protect a high-value reserve wallet from a fully compromised host. Keep automated Agent and Mining wallets low-balance with explicit typed policies and positive caps; use a hardware-backed Wallet Standard account or a reviewed remote custody provider for reserve/Vault funds. Do not change the port mappings to 0.0.0.0, add network_mode: host, mount docker.sock, or enable privileged. Those changes cross the supported local security boundary. Do not change the services to root to work around permissions. Remote access and Docker VPS hosting are not covered by this guide.

Wallets and SAT mining

Local Docker supports signer-owned Solana Agent, Mining, and Vault wallet setup, typed wallet operations, and SAT mining. The native signer is part of the image; users do not install Go or download a second signer binary. To create or manage wallets after initial onboarding:
New signer-owned wallets begin with role baseline v1. Before funding a wallet, review its exact role, live policy/network hashes, destinations, and positive caps. Use a private role template only when intentionally replacing the built-in baseline with narrower or additional authority. For example:
The helper refuses placeholders, group/world-readable policy files, a policy owned by another user, an unhealthy signer, or a digest-confirmation mismatch. It stages the reviewed policy over standard input only in the one-shot admin container’s temporary filesystem; it never gives routine fased-cli the control socket and prints the signer-acknowledged policy afterward. Empty operations, programs, assets, or caps still grant nothing; generic raw signing is not enabled. For a native administrative command not covered by the initial-policy helper, use the explicit profile service. Never use docker compose exec fased-signerd ... admin and never add the control volume back to fased-cli:
This service is deliberately short-lived and networkless. Review every admin command and its input from the local owner terminal before running it. Jupiter Trigger orders use a separate API key owned only by the native signer. The Gateway’s FASED_JUPITER_API_KEY is for ordinary swap quote/transaction crafting and does not enable Trigger. Put the Trigger key in a private file, stream it to the networkless admin container, and restart only the signer:
The one-shot admin receives the key only on stdin and atomically writes it to a dedicated signer-secret volume. Gateway, routine CLI, enrollment, and signer state volumes do not receive that volume. The signer reports only whether Trigger is configured; it never returns the key or its Jupiter JWT. Check or remove the credential without printing it:
If no Trigger key is installed, wallet creation, SOL/SPL transfers, SAT mining, and reviewed Vault operations continue to work; Trigger history/create/cancel fail closed with a configuration error. Vault operations and any separately enabled owner-review lane require a signer-owned approval credential. Agent and Mining automation inside narrow policy does not prompt for a passkey. This is separate from the optional Control UI account passkey. Run the one-shot enrollment service, open the printed http://localhost:18791 URL on the same computer, and touch/approve your authenticator:
The enrollment port is published on host loopback only and exists only while that one-shot command runs. Continue with Agent, wallet, and mining walkthrough. The signer state survives normal container recreation and docker compose down. It does not survive docker compose down -v or manual removal of the project’s fased-signer-state and fased-signer-secrets volumes. Stop both Gateway and signer before an offline backup; never copy the live bbolt database while the signer is running. A complete recovery point must include both named volumes, the host paths in FASED_CONFIG_DIR and FASED_WORKSPACE_DIR, .env, the base and extra Compose files, and the exact immutable image identity. The coordinated update helper below captures and checksums that complete set. 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:

Control UI token + pairing (Docker)

If you see “unauthorized” or “disconnected (1008): pairing required”, fetch a fresh dashboard link and approve the browser device:
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:
Notes:
  • Paths must be shared with Docker Desktop on macOS/Windows.
  • Each entry must be source:target[:options] with no spaces, tabs, or newlines.
  • Prefer :ro unless the container must write to the mounted directory.
  • Container-engine sockets such as docker.sock are rejected because they provide host-level control.
  • 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:
You can combine this with extra mounts:
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:
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:
  1. Bake system deps into the image (repeatable + persistent):
  1. Install Playwright browsers without npx (avoids npm override conflicts):
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):
Do not change Gateway, CLI, or signer to root. On Docker Desktop, use a named home volume if host bind-mount ownership cannot be represented cleanly.

Faster rebuilds

The supplied multi-stage Dockerfile caches Go modules and pnpm dependencies before copying the remaining source. Keep using it: a simplified custom Dockerfile that copies only the Node application will omit fased-signerd and wallet/mining setup will fail closed.

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

Both commands must succeed. If signer health fails, do not retry wallet creation repeatedly; inspect docker compose logs --tail 100 fased-signerd first. The Gateway service already receives FASED_GATEWAY_TOKEN from .env; the health command reads runtime config/env and does not take a --token flag.

Update local Docker

Do not run fased update inside a container, edit FASED_IMAGE first, or manually recreate fased-signerd. A new signer can migrate its bbolt database; an older image may then correctly refuse that migrated state. Copying state.db while the signer is running is not a backup. Run the coordinated helper from the currently running release checkout. Give it a new absolute snapshot directory on durable local storage and an immutable image digest or unique version tag. For the high-assurance registry path, verify the tagged release metadata and OCI image attestation before the updater can stop either service. Install GitHub CLI from your operating system’s trusted package source, confirm gh version, replace vX.Y.Z, and run:
The three downloaded files contain no credentials. The metadata attestation binds the manifest digest and exact signer identity to the repository, tag, workflow, and GitHub-hosted runner. The OCI attestation independently binds that same manifest digest to the Docker release workflow. Stop if a download, attestation, parse, or identity check fails. A unique version tag remains accepted as a convenience path, but it does not provide the same pre-update immutable verification. The helper, in order:
  1. pulls or resolves the target without starting it, reads the packaged Fased version, complete signer release identity, and target Compose definition from that exact image under a networkless, read-only container; it requires the Gateway and signer versions to match, requires a version tag to match that version, rejects a development signer in any registry target, and, when verified release metadata is supplied, requires the embedded commit and signer build-input digest to match it before stopping either service;
  2. validates the target Compose definition with the current owner-controlled .env and optional docker-compose.extra.yml before stopping services;
  3. stops Gateway and then fased-signerd;
  4. verifies the signer is stopped and creates deterministic offline archives of fased-signer-state, fased-signer-secrets, the Gateway config/state bind mount, and the workspace bind mount;
  5. checksums every archive plus the old and target .env/Compose definitions, records both complete signer identities and Gateway versions, and preserves the exact old image ID under a local rollback tag;
  6. atomically installs the target Compose definition and pins FASED_IMAGE to the resolved target image ID;
  7. creates the target signer without starting it and requires it to use both the exact target image ID and the existing signer-state volume;
  8. starts the new signer and requires protocol v2, signer-owned atomic caps, the fixed native-fee reserve, and an exact match for the saved version, commit, build-input digest, and development marker; only then does it start the Gateway and verify its packaged release version.
If target activation fails, the helper stops it, erases the migrated signer volumes and changed Gateway/workspace bind-mount contents while offline, restores and re-verifies every exact snapshot, restores the exact old image, atomically restores the old .env and Compose definition, and starts that saved deployment. The same rollback runs automatically for any error or interruption after the verified offline snapshot is armed. It fails closed without starting either signer when an archive, metadata, volume, bind path, release identity, or exact old image is unavailable. The snapshot contains the signer master key, wallets, policy database, audit state, signer-side API secrets, Gateway credentials and sessions, memory and workspace data, and the Gateway token from .env. The helper creates its directory as 0700 and its files as 0600; keep that directory on owner-only encrypted storage and never upload or share it. This is a same-host transactional rollback snapshot, not a portable or cross-machine disaster-recovery bundle. It depends on the saved local image ID, Docker volume identities, and owner-controlled bind paths from this deployment. For machine-loss recovery, maintain a separate encrypted backup and test its restore on the same OS/architecture and Docker storage layout; do not present the updater snapshot as a portable backup. After success, the deployment directory already contains the target base Compose definition extracted from the image. Verify the version and plugins, then move the rest of the source checkout to the matching tag. Do not edit or discard that Compose file first. Do not delete the snapshot or local rollback image until you have deliberately ended the rollback window:
To roll back later, run the helper from the deployment directory and pass the retained snapshot. It verifies all saved artifacts before touching signer state, then restores the saved pre-update .env, Compose definition, image, and state together:
For a source build, never overwrite fased:local before snapshotting because that destroys the old image reference. Keep the running release checkout in place, build the target tag from a separate checkout under a unique image tag, then invoke the helper from the running release checkout:
The updater rejects latest and fased:local because neither identifies a stable rollback target. Config and workspace survive while FASED_CONFIG_DIR and FASED_WORKSPACE_DIR keep pointing to the same host directories. Signer keys, policies, durable caps, and idempotency state survive in fased-signer-state; signer-side integration secrets survive in fased-signer-secrets. Normal docker compose down preserves them; docker compose down -v destroys the volumes. The complete transaction snapshot is intentionally outside every live path, so it survives a Compose volume rollback.

E2E smoke test (Docker)

QR import smoke test (Docker)

Notes

  • Gateway bind inside the container is lan so Docker port forwarding works; the host-side port remains loopback-only.
  • fased-signerd must be healthy before Gateway or CLI wallet commands run. Missing or incompatible signer binaries fail before onboarding.
  • 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.
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

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:
This builds fased-sandbox-common:bookworm-slim. To use it:
To run the browser tool inside the sandbox, build the browser image:

Custom sandbox image

Build your own image and point config to it:

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.