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)
- First sign-in
- HTTPS with a domain
- Connecting clients
- Accounts (Supabase, optional)
- Backups and restore
- Upgrades
- Environment reference
- Resource sizing
- Security notes
- Without Docker
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 # → okCompose 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/tokenOpen 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.mdHTTPS 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:
- Point a DNS record (e.g.
flockfs.example.com) at the host; open ports 80 and 443. - In
.env:FLOCKFS_DOMAIN=flockfs.example.comandFLOCKFS_PUBLIC_URL=https://flockfs.example.com. docker compose --profile https up -d(pass--profile httpsto laterup/downtoo, or setCOMPOSE_PROFILES=httpsin.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):
- Create a Supabase project; enable the sign-in providers you want, and add your flockfs URL under Authentication → URL Configuration (Site URL / redirect URLs).
- In
.envset all three:FLOCKFS_SUPABASE_URL=https://<project>.supabase.coFLOCKFS_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)
docker compose up -dto 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.
-
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.
-
Create a webhook endpoint at
https://<your-origin>/api/billing/webhook, using Stripe API version2025-06-30.basil, forcheckout.session.completed,checkout.session.async_payment_succeeded,customer.subscription.created,customer.subscription.updated,customer.subscription.deleted,invoice.paidandinvoice.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. -
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.
-
Rebuild and restart flockfs, then open Plan & usage or Upgrade as an org owner. An org currently on
self-hostedmust first be explicitly moved tofreeby 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. -
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 -dSchedule 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 -fTake 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:
| Variable | Default | Purpose |
|---|---|---|
POSTGRES_PASSWORD | — (required) | Database password inside the compose network. URL-safe characters only (it is embedded in FLOCKFS_DATABASE_URL). |
FLOCKFS_PORT | 4317 | Host port for flockfs's HTTP listener. |
FLOCKFS_HOST_BIND | 127.0.0.1 | Host address for that port; 0.0.0.0 exposes plain HTTP to the network. |
FLOCKFS_PUBLIC_URL | empty | Public origin (https://flockfs.example.com) used for OAuth/MCP metadata. Empty: derived from X-Forwarded-Host/Host. |
FLOCKFS_DOMAIN | empty | Domain for the Caddy https profile. |
HTTP_PORT, HTTPS_PORT | 80, 443 | Host ports for Caddy. |
FLOCKFS_SUPABASE_URL, FLOCKFS_SUPABASE_PUBLISHABLE_KEY, FLOCKFS_OWNER_ID | empty | Supabase accounts; all three or none. |
FLOCKFS_SIGNUP_MAX_WORKSPACES | 0 | Global 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_ID | empty | Optional Stripe billing; see above. Independent of Supabase. |
FLOCKFS_DB_CONNECTIONS | 8 | Size of flockfs's PostgreSQL connection pool. |
FLOCKFS_IMAGE | flockfs:local | Image tag to build/run (point it at a registry image to skip building). |
FLOCKFS_BLOB_STORE, FLOCKFS_MAX_FILE_BYTES, FLOCKFS_S3_* | empty | File 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 / flag | Default | Purpose |
|---|---|---|
FLOCKFS_DATABASE_URL / --database-url | postgres://$USER@127.0.0.1:55438/flockfs (the dev cluster) | PostgreSQL URL. Use ?sslmode=require for a managed/remote database. |
--bind | 127.0.0.1:4317 (image: 0.0.0.0:4317) | Listen address. |
--data | data (image: /app/data) | Directory for the workspace key file token (created 0600). |
--web | web/dist (image: /app/web/dist) | Built web app. |
FLOCKFS_TOKEN / --token | from <data>/token | Use this workspace key (≥ 24 characters) instead of the generated file. |
FLOCKFS_PUBLIC_URL, FLOCKFS_SUPABASE_*, FLOCKFS_OWNER_ID, FLOCKFS_DB_CONNECTIONS | As above. | |
--sandboxes, --sandbox-*, FLOCKFS_SANDBOX_ADMIN_TOKEN | off | Public-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. .envholds the database password:chmod 600 .env, never commit it.- The container runs as an unprivileged user (uid 10001) with only
/app/datawritable. - 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 keyFor 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.