Self-hosting

An fctl instance is one Cloudflare Worker (the API, the web app and two Durable Object classes) plus a D1 database, on your own Cloudflare account and domain. Hosts run on your machines and connect to it. Nothing in the code is tied to fctl.sh: everything instance-specific is configuration.

What you need

1. Describe the instance

git clone https://github.com/hyperstructure/fctl && cd fctl
cp deploy/instances/example.toml deploy/instances/acme.toml

Edit deploy/instances/acme.toml:

worker = "fctl"                         # the Worker's name in your account
instance_name = "Acme factory"          # shown in the app and in emails
public_url = "https://fctl.acme.dev"    # its host becomes the Worker's custom domain

[d1]
database_name = "fctl"
database_id = ""                        # filled in by --create
location = "weur"                       # optional primary location

Nothing in this file is secret; commit it to your fork if you like.

2. Deploy

cargo xtask deploy acme --create

This creates the D1 database (recording its id in the instance file), builds the web app and the Worker (Rust compiled to WebAssembly), renders crates/fctl-control/wrangler.deploy.toml, applies the database migrations, and deploys to your domain. Run it again, without --create, for every upgrade.

3. Set the secrets and create the owner

cargo xtask secrets acme

This sets, once:

Sign in, then allow your team under Admin → Access: exact emails (which also creates invite links) or whole domains (*@acme.dev).

With a GitHub App, projects link repositories, agents get short-lived tokens for exactly one repository, and fctl can create new repositories.

  1. Create an app under your organization: Settings → Developer settings → GitHub Apps → New GitHub App. Turn the webhook off. Repository permissions:

    Permission Access Why
    Contents Read and write clone, commit, push
    Pull requests Read and write open and update pull requests
    Issues Read and write agents can read and comment on issues
    Workflows Read and write agents may edit .github/workflows
    Checks, Actions, Commit statuses Read agents can see CI results
    Administration Read and write only for New repository
    Metadata Read required
  2. Install it on the organization (all repositories, or the ones agents may touch).

  3. Generate a private key, and fill in the instance file:

    [github]
    app_id = "123456"
    app_slug = "acme-fctl"
    client_id = "Iv23li…"
    default_owner = "acme"     # where "New repository" creates repositories
  4. Store the key and redeploy:

    cargo xtask secrets acme --github-key ~/Downloads/acme-fctl.private-key.pem
    cargo xtask deploy acme

The key is converted to PKCS#8 and stored as the GITHUB_APP_PRIVATE_KEY secret; it never reaches a host or a sandbox.

5. Google sign-in (optional)

Create an OAuth client of type Web application in the Google Cloud console, with the authorized redirect URI https://fctl.acme.dev/api/v1/auth/google/callback. Then:

[google]
client_id = "1234-abc.apps.googleusercontent.com"
echo "$GOOGLE_CLIENT_SECRET" | cargo xtask secrets acme --set GOOGLE_CLIENT_SECRET
cargo xtask deploy acme

People can then link Google under Settings, and allowlisted people can sign up with it.

6. Email (optional)

Without email, admins copy invite and password-reset links from the app. To send them, use Resend with a verified domain:

[email]
from = "Acme factory <fctl@acme.dev>"
echo "$RESEND_API_KEY" | cargo xtask secrets acme --set RESEND_API_KEY
cargo xtask deploy acme

7. Bring hosts online

On each machine that runs agents, follow Getting started → Bring a machine online:

curl -fsSL https://fctl.sh/install | sh
fctl login fctl.acme.dev
fctl host install --build-image

Hosts download their release from https://fctl.sh/releases by default; set download_base in host.toml to mirror them elsewhere.

Operating it