flockfs

Self-hosting

flockfs is one server binary (flockfs serve: API, live WebSocket, web app, hosted MCP) plus PostgreSQL, which holds every file, version and account. compose.yaml runs both.

Quick start (Docker)

Requires Docker with Compose v2 (Docker Desktop, OrbStack, Colima, or Docker Engine on Linux).

git clone <this repository> flockfs && cd flockfs
cp .env.example .env
sed -i.bak "s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$(openssl rand -hex 32)/" .env && rm .env.bak
docker compose up -d          # first run builds the image (several minutes), then starts db + flockfs
docker compose ps             # both services should become "healthy"
curl http://127.0.0.1:4317/health   # → ok

Compose refuses to start until POSTGRES_PASSWORD is set. PostgreSQL is not published to the host; flockfs listens on 127.0.0.1:4317 (change with FLOCKFS_PORT / FLOCKFS_HOST_BIND in .env). Logs: docker compose logs -f flockfs.

First sign-in

By default flockfs runs in local mode: one shared workspace key (an owner credential), created on first start in the data volume at /app/data/token. Print it with:

docker compose exec flockfs cat /app/data/token

Open http://127.0.0.1:4317 and paste the key. To avoid pasting, mint a single-use, 60-second sign-in link instead:

KEY=$(docker compose exec -T flockfs cat /app/data/token)
curl -s -X POST -H "Authorization: Bearer $KEY" http://127.0.0.1:4317/api/browser-session
# → {"ticket":"…","expires_in":60}; open http://127.0.0.1:4317/#ticket=<ticket>

From the web app's Access panel the owner creates agent tokens (flockfs_…, read-only or read & write, with expiry) for every agent, machine or teammate, so the workspace key itself does not have to be handed out. The CLI is also inside the container, already pointed at the server and key:

docker compose exec flockfs flockfs ls
echo '# Hello' | docker compose exec -T flockfs flockfs put notes/hello.md

HTTPS with a domain

Remote agents, flockfs setup on other machines (it refuses plain http:// for non-loopback servers) and OAuth clients such as claude.ai need flockfs at a public HTTPS URL. The bundled https profile runs Caddy with an automatic Let's Encrypt certificate:

  1. Point a DNS record (e.g. flockfs.example.com) at the host; open ports 80 and 443.
  2. In .env: FLOCKFS_DOMAIN=flockfs.example.com and FLOCKFS_PUBLIC_URL=https://flockfs.example.com.
  3. docker compose --profile https up -d (pass --profile https to later up/down too, or set COMPOSE_PROFILES=https in .env).

Caddy proxies WebSockets and sets X-Forwarded-*. Leave FLOCKFS_HOST_BIND=127.0.0.1 so the plain-HTTP port stays private. Any other TLS proxy (Traefik, nginx, a Cloudflare Tunnel) works the same way: forward everything, including WebSocket upgrades on /live, to flockfs:4317, allow request bodies up to 50 MiB (flockfs's own limit; 25 MiB binaries travel as base64, e.g. nginx client_max_body_size 50m), and set FLOCKFS_PUBLIC_URL if the proxy rewrites Host.

Connecting clients

Install the CLI on each machine (./scripts/install-cli.sh from a checkout, or the archive from ./scripts/package-cli.sh); it needs neither Docker nor PostgreSQL. Use an agent token created under Access (or the workspace key on your own machine).

# Sync a folder (~/flockfs by default), always on, verified both ways
FLOCKFS_URL=https://flockfs.example.com FLOCKFS_TOKEN=flockfs_… flockfs setup
# On the Docker host itself, with the key:
docker compose exec -T flockfs cat /app/data/token > ~/.flockfs-key && chmod 600 ~/.flockfs-key
flockfs setup --url http://127.0.0.1:4317 --token-file ~/.flockfs-key

# Claude Code, stdio MCP
claude mcp add flockfs -- "$HOME/.local/bin/flockfs" mcp --url https://flockfs.example.com --token-file ~/.flockfs-key
# Claude Code / any MCP client, hosted MCP over HTTP
claude mcp add --transport http flockfs https://flockfs.example.com/mcp --header "Authorization: Bearer $FLOCKFS_TOKEN"

claude.ai custom connector (OAuth, no token to copy): Settings → Connectors → Add custom connector with https://flockfs.example.com/mcp; a workspace owner approves it on flockfs's consent page. claude.ai connects from Anthropic's servers, so this only works with a public HTTPS URL and FLOCKFS_PUBLIC_URL set. See OAuth for remote MCP clients.

Accounts (Supabase, optional)

Local mode (one shared key) suits a single person or a trusted team. For per-person sign-in with owner/editor/viewer roles and email invites, flockfs uses Supabase Auth (only for identity; files stay in your PostgreSQL):

  1. Create a Supabase project; enable the sign-in providers you want, and add your flockfs URL under Authentication → URL Configuration (Site URL / redirect URLs).
  2. In .env set all three:
    • FLOCKFS_SUPABASE_URL=https://<project>.supabase.co
    • FLOCKFS_SUPABASE_PUBLISHABLE_KEY=sb_publishable_… (the public key; flockfs refuses secret keys)
    • FLOCKFS_OWNER_ID=<your Supabase user UUID> (Authentication → Users; sign up once first)
  3. docker compose up -d to apply. The shared key is disabled; the owner invites others under Access. Agent tokens and OAuth connections work in both modes.

FLOCKFS_OWNER_ID seeds the first owner once per database; afterwards roles are managed in the app.

Stripe billing (optional)

Stripe billing works independently of Supabase, including in shared-key mode. Leave the Stripe settings empty to keep the existing manually managed plans. Billing is available for free and team orgs; enterprise and self-hosted orgs keep their operator-managed plans.

  1. In the intended Stripe account, create a Team product with positive USD recurring, per-seat monthly and/or yearly prices. At least one price is needed. The app and website display the actual Stripe amounts, rather than fixed dollar values in the source code. Start with Stripe test-mode prices and credentials on a separate test installation/database.

  2. Create a webhook endpoint at https://<your-origin>/api/billing/webhook, using Stripe API version 2025-06-30.basil, for checkout.session.completed, checkout.session.async_payment_succeeded, customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, invoice.paid and invoice.payment_failed. Configure the Stripe customer portal to allow payment-method changes, invoices, cancellation and seat changes for the Team prices. Use a dedicated portal configuration for flockfs if the Stripe account also bills other products.

  3. Set these server-side variables in .env:

    FLOCKFS_BILLING_PUBLIC_URL=https://app.example.com
    FLOCKFS_STRIPE_SECRET_KEY=sk_test_...
    FLOCKFS_STRIPE_WEBHOOK_SECRET=whsec_...
    FLOCKFS_STRIPE_MONTHLY_PRICE_ID=price_...
    FLOCKFS_STRIPE_YEARLY_PRICE_ID=price_...
    FLOCKFS_STRIPE_PRODUCT_ID=prod_...
    FLOCKFS_STRIPE_PORTAL_CONFIGURATION_ID=bpc_...

    The public URL must be an HTTPS origin without a path, query or fragment. Omit either price to offer only the other billing period. All credentials and prices must belong to the same account and mode; no Stripe secret is sent to the browser. The portal configuration is optional; leaving it empty uses Stripe's account-wide default. Set the stable product ID before rotating price IDs: existing subscriptions on older prices of that product retain access. A price update creates a new Stripe Price and selects its ID for new checkouts; it does not automatically change existing subscriptions.

  4. Rebuild and restart flockfs, then open Plan & usage or Upgrade as an org owner. An org currently on self-hosted must first be explicitly moved to free by the server operator (flockfs org plan free) if it should use Stripe instead of manual unlimited access. Choose enough seats for all current people and pending invites. Checkout and Manage billing open Stripe-hosted pages. The shared workspace key can manage billing in local mode; agent tokens and drive owners who are not org owners cannot.

  5. Verify test checkout, webhook delivery, plan activation, seat limits and cancellation. Then configure the production installation with matching live-mode credentials, prices and webhook signing secret. Test and live customer/subscription identifiers are separate: do not reuse a test installation's billing records in production.

Only a verified webhook reconciled against Stripe's current subscription and paid invoice activates Team. Returning from checkout does not activate it. Cancellation or non-paid state returns the org to Free; current files are retained, growth is limited by the Free caps, and history retention follows the plan. Stripe retries unsuccessful webhook deliveries.

Purchased seats cap new human memberships/invites and determine both Team pools: 20 GiB of current content and 5 GiB of retained history per seat, shared across the org. History is kept for up to 365 days within its retained-payload budget. Agent connections are included without extra seats; they share the workspace's usage limits. Existing people are retained if a seat reduction puts the org over its cap, and further additions are blocked. Seat quantities are managed explicitly in Stripe; flockfs does not automatically increase them or charge for invitations. In shared-key mode, individual humans are not identified, so choose the seat count for the people sharing the workspace.

For public personal workspaces, set FLOCKFS_SIGNUP_MAX_WORKSPACES to a deliberate admission ceiling (default 0, closed). Verified new human accounts receive one private free workspace; existing invited accounts retain their shared access. Start with a small ceiling and inspect accounts-launch.md and capacity.md before expanding. Google/GitHub buttons appear only after their OAuth providers are configured and enabled in Supabase. Configure custom SMTP before broad email signup or password recovery.

Backups and restore

Everything that matters is in PostgreSQL — unless you turned on a blob store (storage.md): with FLOCKFS_BLOB_STORE=fs, file bodies are in the data volume (/app/data/blobs) and must be backed up with the dump (docker compose cp flockfs:/app/data/blobs ./blobs-backup or a volume snapshot, taken after the dump, so every blob the dump references is in it); with s3, use the bucket's versioning or replication. The data volume also holds the local-mode workspace key (back it up too, or you will need to read a new key after restoring).

# Backup (consistent, online)
docker compose exec -T db pg_dump -U flockfs -d flockfs --format=custom > flockfs-$(date +%F).dump
docker compose cp flockfs:/app/data/token ./flockfs-token.backup     # local mode only

# Restore (into a fresh stack, or in place)
docker compose up -d db
docker compose stop flockfs
docker compose exec -T db pg_restore -U flockfs -d flockfs --clean --if-exists --no-owner < flockfs-2026-10-04.dump
docker compose up -d

Schedule the pg_dump line (cron, systemd timer) and copy the dumps off the host. History is never pruned, so dumps grow with edits. Avoid copying the raw db volume while PostgreSQL runs.

Upgrades

git pull
docker compose build --pull
docker compose up -d          # recreates flockfs; schema changes apply automatically at startup
docker image prune -f

Take a pg_dump first. flockfs updates its schema idempotently when it starts; there is no separate migration step and no downgrade path, so restore the dump to go back.

PostgreSQL minor updates arrive with docker compose pull db && docker compose up -d. A major upgrade (e.g. 18 → 19) cannot reuse the data volume: dump, stop, change the image tag in compose.yaml, remove the db volume (docker volume rm <project>_db), start the new db and restore the dump.

Environment reference

Variables read from .env by compose.yaml:

VariableDefaultPurpose
POSTGRES_PASSWORD— (required)Database password inside the compose network. URL-safe characters only (it is embedded in FLOCKFS_DATABASE_URL).
FLOCKFS_PORT4317Host port for flockfs's HTTP listener.
FLOCKFS_HOST_BIND127.0.0.1Host address for that port; 0.0.0.0 exposes plain HTTP to the network.
FLOCKFS_PUBLIC_URLemptyPublic origin (https://flockfs.example.com) used for OAuth/MCP metadata. Empty: derived from X-Forwarded-Host/Host.
FLOCKFS_DOMAINemptyDomain for the Caddy https profile.
HTTP_PORT, HTTPS_PORT80, 443Host ports for Caddy.
FLOCKFS_SUPABASE_URL, FLOCKFS_SUPABASE_PUBLISHABLE_KEY, FLOCKFS_OWNER_IDemptySupabase accounts; all three or none.
FLOCKFS_SIGNUP_MAX_WORKSPACES0Global lifetime cap on personal free workspaces; existing accounts work when new provisioning is closed.
FLOCKFS_BILLING_PUBLIC_URL, FLOCKFS_STRIPE_SECRET_KEY, FLOCKFS_STRIPE_WEBHOOK_SECRET, FLOCKFS_STRIPE_MONTHLY_PRICE_ID, FLOCKFS_STRIPE_YEARLY_PRICE_ID, FLOCKFS_STRIPE_PRODUCT_ID, FLOCKFS_STRIPE_PORTAL_CONFIGURATION_IDemptyOptional Stripe billing; see above. Independent of Supabase.
FLOCKFS_DB_CONNECTIONS8Size of flockfs's PostgreSQL connection pool.
FLOCKFS_IMAGEflockfs:localImage tag to build/run (point it at a registry image to skip building).
FLOCKFS_BLOB_STORE, FLOCKFS_MAX_FILE_BYTES, FLOCKFS_S3_*emptyFile bodies in object storage (fs, s3; MinIO via --profile minio). See storage.md.

Read by flockfs serve itself (set directly when running without compose):

Variable / flagDefaultPurpose
FLOCKFS_DATABASE_URL / --database-urlpostgres://$USER@127.0.0.1:55438/flockfs (the dev cluster)PostgreSQL URL. Use ?sslmode=require for a managed/remote database.
--bind127.0.0.1:4317 (image: 0.0.0.0:4317)Listen address.
--datadata (image: /app/data)Directory for the workspace key file token (created 0600).
--webweb/dist (image: /app/web/dist)Built web app.
FLOCKFS_TOKEN / --tokenfrom <data>/tokenUse this workspace key (≥ 24 characters) instead of the generated file.
FLOCKFS_PUBLIC_URL, FLOCKFS_SUPABASE_*, FLOCKFS_OWNER_ID, FLOCKFS_DB_CONNECTIONSAs above.
--sandboxes, --sandbox-*, FLOCKFS_SANDBOX_ADMIN_TOKENoffPublic-demo mode: a throwaway workspace per visitor; not for a normal install. See Sandboxes.

Resource sizing

Small. A personal or team workspace (a few people, a handful of agents) runs comfortably on 1 vCPU and 1 GiB RAM for both containers; idle, flockfs uses under 10 MiB and PostgreSQL about 45 MiB (measured on arm64). The image is about 190 MB on disk (≈45 MB compressed). Memory grows with concurrently open live documents and connections; CPU with merge and search traffic. Disk is dominated by history (every version is kept): plan for several times the size of your files, and watch pg_database_size('flockfs'). Building the image needs about 2–4 GiB of RAM and ~5 GiB of disk on the build machine; use a prebuilt image (FLOCKFS_IMAGE) on tiny hosts.

Security notes

Hosted Free and Team workspaces share traffic budgets across every drive and credential. GET /api/usage reports the current technical caps; throttles do not create usage charges. These counters are process-local: run one hub process until shared coordination is added. Hosted live frames and MCP request envelopes are limited to 8 MiB; use streaming raw upload routes for larger file bodies.

For local storage, optionally set FLOCKFS_MIN_FREE_BYTES to a disk reserve (for example, 10737418240 for 10 GiB) and FLOCKFS_CAPACITY_PATH to the filesystem holding the bodies and database. Docker defaults the path to /app/data. Empty reserve disables the check. Low headroom refuses content writes and uploads with 503 storage_capacity_low and Retry-After: 60; reads, exports, access administration and retention cleanup stay available. Checks do not reserve space and do not protect a remote database or object store. Monitor those services separately and scale storage before expanding hosted signups.

  • The workspace key in local mode is an owner credential. Hand out agent tokens instead, give them the narrowest role (read-only where possible) and an expiry, and revoke them under Access.
  • Serve over HTTPS for anything beyond loopback: keys and tokens travel as bearer headers. The default compose publishes flockfs only on 127.0.0.1, and PostgreSQL not at all.
  • .env holds the database password: chmod 600 .env, never commit it.
  • The container runs as an unprivileged user (uid 10001) with only /app/data writable.
  • Sandboxes (--sandboxes) are for public demos; do not enable them on a real workspace.
  • flockfs is a prototype: there is no rate limiting on most API routes and no quota per user, so put it behind a proxy you control if it faces the internet.

Without Docker

Use an existing PostgreSQL (the compose stack uses 18; recent majors should work). The pg_trgm extension, part of the standard contrib package, must be available; flockfs creates it on start, so the database user needs to own the database).

# Build (Rust per rust-toolchain.toml, Node 22+)
npm ci --prefix sdk && npm run build --prefix sdk
npm ci --prefix web && npm run build --prefix web
cargo build --release --locked
install -m 0755 target/release/flockfs /usr/local/bin/flockfs
sudo mkdir -p /opt/flockfs && sudo cp -R web/dist /opt/flockfs/web

# Database (once)
sudo -u postgres createuser --pwprompt flockfs
sudo -u postgres createdb --owner flockfs flockfs

# Run
export FLOCKFS_DATABASE_URL='postgres://flockfs:PASSWORD@127.0.0.1:5432/flockfs'
flockfs serve --bind 127.0.0.1:4317 --data /var/lib/flockfs --web /opt/flockfs/web
cat /var/lib/flockfs/token     # the workspace key

For a service, run it under systemd as a dedicated user with Environment=FLOCKFS_DATABASE_URL=… (or an EnvironmentFile= with mode 0600), Restart=on-failure and KillSignal=SIGINT (flockfs shuts down gracefully on SIGINT). Put Caddy or nginx in front for HTTPS as described above.

On this page