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 noinstall.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.
- Containerized Gateway (full Fased in Docker)
- Per-session Agent Sandbox (host gateway + Docker-isolated agent tools)
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 installmay be OOM-killed with exit 137. - Enough disk for images + logs
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:
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:Install from the public image (recommended)
Open the latest stable entry on GitHub Releases, copy its version without the leadingv, then clone and run that exact release. Keep the source
tag and image tag identical:
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:
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
FASED_IMAGE— use a selected image instead of buildingfased:localFASED_DOCKER_APT_PACKAGES— install extra apt packages during buildFASED_EXTRA_MOUNTS— add extra host bind mountsFASED_HOME_VOLUME— persist/home/nodein a named volume
- Open
http://localhost:18789/in your browser. - Paste the token from
.envor 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.
~/.fased/~/.fased/workspace
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: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.1only - packages
fased-signerdwith the matching image and runs it as a separate, non-rootfased-signerdservice 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-climount only the application socket - exposes control access only through explicit, one-shot
fased-signer-adminandfased-signer-enrollprofile 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
nodeuser - 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
.envwith user-only permissions - excludes local
.env*,.fased, SSH/private keys, and common credential directories from the image build context
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: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:
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:
http://localhost:18791 URL on the same computer, and touch/approve your
authenticator:
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:Setup after Docker starts
Use the browser UI for normal setup:- open
http://localhost:18789/ - choose the default Agent, shown as Assistant
- configure model auth in Agent > Models
- test one message in Chat
- add channels in Agent > Channels
- add API connectors in Agent > Services
Extra mounts (optional)
If you want to mount additional host directories into the containers, setFASED_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:
- Paths must be shared with Docker Desktop on macOS/Windows.
- Each entry must be
source:target[:options]with no spaces, tabs, or newlines. - Prefer
:rounless the container must write to the mounted directory. - Container-engine sockets such as
docker.sockare rejected because they provide host-level control. - If you edit
FASED_EXTRA_MOUNTS, rerundocker-setup.shto regenerate the extra compose file. docker-compose.extra.ymlis 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:
- Named volumes must match
^[A-Za-z0-9][A-Za-z0-9_.-]*$. - If you change
FASED_HOME_VOLUME, rerundocker-setup.shto 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), setFASED_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:
- This accepts a space-separated list of apt package names.
- If you change
FASED_DOCKER_APT_PACKAGES, rerundocker-setup.shto rebuild the image.
Power-user / full-featured container (opt-in)
The default Docker image is minimal and runs as the non-rootnode 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
- Persist
/home/nodeso browser downloads and tool caches survive:
- Bake system deps into the image (repeatable + persistent):
- Install Playwright browsers without
npx(avoids npm override conflicts):
FASED_DOCKER_APT_PACKAGES instead of using --with-deps at runtime.
- Persist Playwright browser downloads:
- Set
PLAYWRIGHT_BROWSERS_PATH=/home/node/.cache/ms-playwrightindocker-compose.yml. - Ensure
/home/nodepersists viaFASED_HOME_VOLUME, or mount/home/node/.cache/ms-playwrightviaFASED_EXTRA_MOUNTS.
Permissions + EACCES
The image runs asnode (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):
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 omitfased-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, DiscordOpenAI Codex OAuth (headless Docker)
If you pick OpenAI Codex OAuth from Agent > Models, it opens a browser URL and tries to capture a callback onhttp://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 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 runfased 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:
- 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;
- validates the target Compose definition with the current owner-controlled
.envand optionaldocker-compose.extra.ymlbefore stopping services; - stops Gateway and then
fased-signerd; - 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; - 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; - atomically installs the target Compose definition and pins
FASED_IMAGEto the resolved target image ID; - creates the target signer without starting it and requires it to use both the exact target image ID and the existing signer-state volume;
- 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.
.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:
.env, Compose definition, image,
and state together:
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:
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
lanso Docker port forwarding works; the host-side port remains loopback-only. fased-signerdmust be healthy before Gateway or CLI wallet commands run. Missing or incompatible signer binaries fail before onboarding.- Dockerfile CMD uses
--allow-unconfigured; mounted config withgateway.modenotlocalwill 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.- one sandbox per agent by default
- sandbox workspace under
~/.fased/sandboxes - Docker network disabled by default
- host browser/camera/canvas are blocked by default
denytool policy wins overallowscope: "shared"disables cross-session isolation
Build the default sandbox image
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:fased-sandbox-common:bookworm-slim. To use it:
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
browserin sandbox breaks isolation (browser runs on host).
Troubleshooting
- Image missing: build with
scripts/sandbox-setup.shor setagents.defaults.sandbox.docker.image. - Container not running: it will auto-create per session on demand.
- Permission errors in sandbox: set
docker.userto 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/profileand may reset PATH. Setdocker.env.PATHto 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.