@fusekit-cloud/fusekit
v0.2.2
Published
Fusekit CLI — deploy apps and manage Fusekit installations.
Readme
Fusekit
Fusekit is an internal application platform that operates in two modes: a self-hosted single-tenant data plane inside a customer VPC, or a hosted multi-tenant cloud platform (fusekit.cloud). It deploys static web apps and provides those apps with authenticated user, Postgres document, object-file, and optional AI APIs.
# Log in (add --cloud flag for Fusekit Cloud)
fusekit login [--cloud]
fusekit deploy ./dist --app expensesThe resulting app is available at:
# Self-hosted mode:
https://expenses.apps.customer-company.internal
# Hosted cloud mode:
https://expenses.your-tenant.apps.fusekit.cloudIn self-hosted mode, Fusekit has no vendor-hosted control plane. Customer identity, metadata, application data, assets, and uploaded files remain inside customer infrastructure.
Documentation, SDK & AI tooling
Everything a developer needs to build on Fusekit is served under fusekit.cloud:
- Docs — https://docs.fusekit.cloud (Mintlify site, source in
web/docs/). - CLI —
curl -fsSL https://fusekit.cloud/install | sh, ornpx @fusekit-cloud/fusekitif you have Node (prebuilt binary from npm). - SDK —
npm install @fusekit-cloud/sdk(orhttps://esm.sh/@fusekit-cloud/sdk). - MCP server —
npx @fusekit-cloud/mcplets AI agents (Claude Code, Cursor, Claude Desktop) scaffold, inspect, and deploy Fusekit apps. Source inmcp/. - AI skills — importable agent skills in
skills/, downloadable athttps://fusekit.cloud/skills/fusekit-app-builder.zip.
Architecture
Self-Hosted Mode
Browser
-> customer wildcard DNS
-> Caddy (strips headers, verifies session, adds X-Fusekit-Proxy-Secret)
-> OAuth2 Proxy
-> Fusekit Server
-> PostgreSQL (pgx/Postgres)
-> Go CDK blob bucket (s3:// / gs:// / azblob:// — one binary, any cloud)
-> optional LiteLLM
Fusekit CLI
-> bearer-token deploy API
-> Fusekit ServerIn self-hosted mode, Caddy removes user-supplied identity headers, verifies browser sessions through OAuth2 Proxy, copies verified identity headers, and adds X-Fusekit-Proxy-Secret. The CLI routes bypass browser SSO and use Authorization: Bearer <FUSEKIT_DEPLOY_TOKEN>.
The root of a self-hosted install is the employee launcher — a searchable home where every signed-in person sees the company's internal apps (name, description, owner, last updated) and opens them in one click. Inside the company everyone is trusted, so every member sees every deployed app; there are no per-app permissions. Admins manage apps from the console at /dashboard.
Hosted Cloud Mode (fusekit.cloud)
Browser
-> wildcard DNS (*.apps.fusekit.cloud + fusekit.cloud)
-> Caddy / Cloudflare load balancer
-> Stateless Fusekit Server pool (verifies GoTrue JWTs, maps tenant)
-> Supabase (Database, Auth, and Storage/S3-compatible API)
-> optional LiteLLM
Fusekit CLI (fusekit login --cloud)
-> token-based deploy API (X-Fusekit-Org + Bearer token)
-> Fusekit Server poolIn cloud mode, Fusekit bypasses the external OAuth2 Proxy and validates browser sessions and API calls using Supabase Auth (GoTrue JWTs) (via the Authorization: Bearer header or first-party fusekit_session cookie). Request paths resolve tenant scoping dynamically from host headers, which are mapped to orgs in Postgres.
Repository
cmd/fusekit-server/ Go HTTP server (supports both self-hosted and cloud modes)
cmd/fusekit/ Go CLI
internal/ auth, deploy, DB, storage, audit, AI, limits, tenant, and realtime packages
migrations/ embedded SQL migrations (001_initial … 006_custom_domains)
sdk/ @fusekit-cloud/sdk
web/admin/ React/Vite admin console (includes cloud control panel features)
examples/hello-react/ example Fusekit application
examples/api-conformance/ deployable smoke test exercising every feature API
deploy/ Compose, Caddy, OAuth2 Proxy, MinIO, LiteLLM config, Docker Cloud config, Helm chartRoles and Placements
One binary serves every placement, selected by FUSEKIT_ROLE (see
docs/ARCHITECTURE.md):
FUSEKIT_ROLE=all— control plane + shared multi-tenant runtime in one stateless pool (Fusekit Cloud).FUSEKIT_ROLE=runtime— the portable single-tenant runtime, in two sub-modes:- disconnected (classic self-hosted): license key, static deploy token, no control-plane dependency;
- connected BYOC: set
FUSEKIT_CONTROL_PLANE_URL=https://fusekit.cloudandFUSEKIT_RUNTIME_TOKEN=frt_...(created in the cloud console under Runtimes). The runtime registers on boot, heartbeats aggregate health/usage outbound-only, and accepts short-lived signed deploy tokens minted by the control plane. App data never leaves the runtime, and it keeps serving if fusekit.cloud is unreachable.
FUSEKIT_MODE remains a backward-compatible alias (cloud → all, self-hosted → runtime).
Deploy routing
fusekit deploy asks the control plane where the workspace's runtime lives
(POST /api/deploy/target), receives {deployment_mode, runtime_url,
base_domain, deploy_token} with a 10-minute Ed25519-signed deploy token, and
uploads the bundle directly to that runtime. Servers without the endpoint
(disconnected self-hosted) are deployed to directly with the configured
static token. Multi-instance cloud pools must share
FUSEKIT_DEPLOY_TOKEN_SIGNING_KEY (base64url 32-byte Ed25519 seed);
verification keys are published at GET /api/cp/jwks.
Local Development
Fusekit can be run in one of two modes, selected via the FUSEKIT_MODE environment variable:
self-hosted(default): Runs the single-tenant server using OAuth2 Proxy headers and local database configuration.cloud: Runs the multi-tenant hosted SaaS server using Supabase for database, authentication, and object storage.
Requirements:
- Docker Desktop with Compose
- Node.js 20 or newer
- Go 1.25 or newer when building the CLI outside Docker
Start the development stack:
To run in self-hosted mode:
make dev-self-hostedTo run in cloud (multi-tenant SaaS) mode:
- Configure your Supabase project credentials in
deploy/.env.cloud(which is copied fromdeploy/.env.cloud.exampleautomatically on first run). - Start the stack:
make dev-cloud- Configure your Supabase project credentials in
Open http://localhost:8080. The development Caddy configuration uses a fixed identity:
[email protected]
groups: adminDo not use development identity mode in production. It is available only when FUSEKIT_DEV_MODE=true, validates the internal proxy secret, and is intended solely to make the local stack runnable without an external IdP.
For the lowest-friction path, use the CLI wizard instead of editing Compose files. This is the same arc as a real install, with a local developer identity standing in for SSO:
fusekit setup --dev --dir ./fusekit-install # generate (no DNS/IdP needed in dev)
fusekit install --install-dir ./fusekit-install
fusekit doctor --install-dir ./fusekit-install
fusekit init myapp # scaffold a starter app
fusekit deploy ./myapp --app myapp # open http://myapp.localhost:8080Build the CLI and example:
mkdir -p bin
go build -o bin/fusekit ./cmd/fusekit
npm install
npm run buildConfigure and deploy (Self-Hosted):
bin/fusekit login \
--server http://localhost:8080 \
--token dev-deploy-token-change-me
bin/fusekit deploy examples/hello-react/dist --app helloOpen http://hello.localhost:8080. If the local resolver does not map subdomains of localhost automatically, add hello.localhost to /etc/hosts or use a local DNS resolver.
Configure and deploy (Hosted Cloud):
# Log in to local cloud dev server (or omit --server to log in to production fusekit.cloud)
bin/fusekit login --server http://localhost:8080 --token <your-deploy-token>
# Or log in to production
bin/fusekit login --cloud
bin/fusekit deploy examples/hello-react/dist --app helloThe MinIO console is available at http://localhost:9001 in development. Credentials come from AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
Conformance test (every feature API, any target)
examples/api-conformance is a one-page app that exercises every Fusekit
feature API — auth, document DB, DB realtime, file storage, AI, and realtime
rooms — and reports pass / warn / fail per feature. scripts/conformance.sh
(wrapped by make) builds the CLI, builds the app, brings up (or points at) the
right stack, deploys the app, and prints the URL to open and hit Run all
checks:
# Local, self-hosted single-tenant runtime (non-managed). Fully automatic:
# brings the stack up, deploys, signs you in as the dev identity.
make test-local-self-hosted # add FLAGS=--open to auto-open the browser
# Local, managed multi-tenant mode (needs Supabase creds in deploy/.env.cloud).
make test-local-managed
# Managed production fusekit.cloud (device-flow login).
make test-cloud TENANT=<your-tenant>
make test-down # tear the local stack downRun scripts/conformance.sh --help for flags (--open, --no-build) and env
overrides (APP, TENANT, SERVER, TOKEN). Each check writes only to
throwaway collections / files and cleans up after itself.
Self-Host Quickstart (≈15 minutes)
From nothing to a shared internal app, in one arc. Each command prints the next one, so you can follow the CLI alone.
0. Install the CLI
curl -fsSL https://fusekit.cloud/install | shThe install script is served by fusekit-server (GET /install): it detects your OS and CPU architecture, downloads the matching GitHub release, verifies its SHA-256 checksum, and installs only the fusekit binary.
1. Generate the installation — fusekit setup
fusekit setup --dir /opt/fusekitThe wizard asks for your domain, identity provider (Microsoft Entra ID, Okta, Google Workspace, or generic OIDC), and object storage, then generates every secret, writes a private .env and Compose bundle, calculates the exact OIDC callback URL, saves your CLI deploy credentials, and prints the exact DNS records to create. It spells out both the apex (apps.company.internal) and wildcard (*.apps.company.internal) A/AAAA records pointing at this host, and calls out that one TLS certificate must cover both names (a single wildcard cert) — a cert for only the apex loads the launcher but breaks every app. Finish SSO using the generated sso-setup-instructions.md.
If you already hold a wildcard certificate (common with an internal CA), pass it to setup and it wires it straight into Caddy — no Caddyfile editing:
fusekit setup --dir /opt/fusekit --tls-cert wildcard.crt --tls-key wildcard.keySetup verifies the cert actually covers *.apps.company.internal (rejecting an apex-only cert up front), copies it to tls/ in the install, mounts it read-only, and adds the tls directive. Omit the flags to keep Caddy's automatic HTTPS — but note public ACME cannot issue for private .internal names, so internal deployments should supply a cert or configure a DNS-01 challenge.
2. Start it — fusekit install
fusekit install --install-dir /opt/fusekitRuns a preflight, brings up the stack, waits for readiness, and prints Fusekit is live at https://….
3. Confirm it's healthy — fusekit doctor
fusekit doctor --install-dir /opt/fusekitdoctor checks Docker and Compose, configuration and placeholder secrets, the SSO client and Compose profile, the referenced Caddyfile, health/readiness, and — because every app is served from its own subdomain — DNS and TLS specifically for subdomain routing: that the apex resolves, that *.{base} resolves to the same host (not just anything), and that the certificate actually covers app hosts (*.{base}), not only the apex. The last check catches the silent failure where the launcher loads but every app fails its TLS handshake. When a wildcard cert was supplied to setup (FUSEKIT_TLS_CERT_FILE is set), the apex TLS check verifies the certificate's names and expiry rather than its chain — an internal CA the doctor host doesn't trust is fine, since client devices trust it. Each failure prints an exact fix on its line; re-run until it's all green.
4. Ship your first app — fusekit init + fusekit deploy
fusekit init myapp # scaffolds a zero-build starter app
fusekit deploy ./myapp --app myapp # uploads itinit writes a single self-contained index.html (no npm, no build step) that already uses SSO identity and the shared database. deploy prints Your app is live at https://myapp.apps.company.internal.
Prefer the browser? Open the console and use Fusekit Drop — drag any folder of HTML/files (no build needed) and it deploys through the same pipeline. A folder with an index.html at its root is served as the site directly.
5. Open it via SSO and share it
Open the printed URL — your IdP signs you in — and send the link to a teammate. They sign in with the same SSO and see the same live list. That's the whole loop.
For automation, every setup prompt has a flag:
fusekit setup \
--non-interactive \
--dir /opt/fusekit \
--domain apps.company.internal \
--provider entra \
--issuer-url https://login.microsoftonline.com/TENANT_ID/v2.0 \
--client-id CLIENT_ID \
--client-secret CLIENT_SECRET \
--admin-groups platform-admins \
--storage bucket \
--bucket-url gs://company-fusekitThe bucket preset takes any Go CDK bucket URL (s3:// for AWS/MinIO/R2,
gs://, or azblob://), so this command targets any cloud by changing only the URL.
Installation reference
DNS. Create two records; no per-app changes are ever required:
apps.customer-company.internal -> Caddy or customer load balancer
*.apps.customer-company.internal -> Caddy or customer load balancerTLS. For private internal domains, provide a wildcard certificate from the customer internal CA — pass it to fusekit setup --tls-cert/--tls-key and it is validated (must cover *.{domain}), copied into tls/, and mounted into Caddy automatically. For public domains, use ACME with a DNS challenge — public HTTP ACME cannot be assumed to work for private .internal names. Local TLS testing can use mkcert. Either way fusekit doctor confirms the served certificate actually covers app hosts before you ship.
Production OIDC (setup writes these; fill in any [configure-later-…] placeholders, which doctor flags):
FUSEKIT_DEV_MODE=false
FUSEKIT_CADDYFILE=./Caddyfile
OAUTH2_PROXY_PROVIDER=oidc
OAUTH2_PROXY_OIDC_ISSUER_URL=https://idp.customer.example
OAUTH2_PROXY_CLIENT_ID=...
OAUTH2_PROXY_CLIENT_SECRET=...
OAUTH2_PROXY_COOKIE_SECRET=...Use deploy/Caddyfile, not deploy/Caddyfile.dev, in production. Configure OAuth2 Proxy group claims for the selected provider and set the admin groups:
FUSEKIT_ADMIN_GROUPS=platform-admins,security-adminsOnly users in one of those exact groups can call /admin/api/* or perform destructive browser admin actions. All authenticated employees can access deployed applications and their own app's runtime APIs.
doctor also serves CI and cloud installs: --json emits machine-readable results; in cloud mode it probes nested wildcard DNS ({app}.{tenant}.{base}), and --app-host {app}.{tenant}.{base} verifies a real deployed host resolves and serves a valid certificate (on-demand TLS issuance succeeded).
Hosted Multi-Tenant SaaS Mode (Fusekit Cloud)
Hosted mode (FUSEKIT_MODE=cloud) turns Fusekit into a multi-tenant application platform powered by Supabase.
Core Tenancy Architecture
- Tenant Isolation: Achieved using
org_idcolumns in the database. Requests are scoped automatically via the Go query-layer. Row-level security (RLS) is deferred in the database layer. - Default Organization: A well-known default organization (
00000000-0000-0000-0000-000000000001) is automatically seeded. Self-hosted deployments backfill into this organization, allowing seamless upgrade paths. - Hostname Scheme: Apps are served at
{app_slug}.{tenant_slug}.apps.fusekit.cloud(the app is a child label of the tenant). These two-label-deep hosts fall outside the single-label*.apps.fusekit.cloudwildcard cert, so they receive per-host certificates via Caddy on-demand TLS, gated by/internal/tls-allowed. Verified custom domains serve the same apps at{app_slug}.{custom_domain}. - Cloud Limits: Cloud mode skips license verification; organizations are bound to plans (e.g.,
free,unlimited) configured in theplanstable which restrictmax_apps,max_members,max_storage_bytes, andmax_upload_bytes.
Cloud Configuration
To run the server in cloud mode, set the following environment variables:
FUSEKIT_MODE=cloud
FUSEKIT_CLOUD_ROOT_DOMAIN=fusekit.cloud
FUSEKIT_PUBLIC_BASE_DOMAIN=apps.fusekit.cloud
FUSEKIT_DATABASE_URL=postgresql://postgres:[email protected]:6543/postgres?pgbouncer=true
FUSEKIT_LISTEN_DATABASE_URL=postgresql://postgres:[email protected]:5432/postgres
FUSEKIT_BLOB_BUCKET_URL=s3://fusekit?endpoint=https://xxx.storage.supabase.co/storage/v1/s3&use_path_style=true
FUSEKIT_SUPABASE_URL=https://xxx.supabase.co
FUSEKIT_SUPABASE_PUBLISHABLE_KEY=eyJhbGci...
# Secret Key or JWKS URL to verify incoming GoTrue JWTs (JWKS is auto-inferred if not specified)
FUSEKIT_SUPABASE_SECRET_KEY=your-secret-key[!NOTE]
FUSEKIT_LISTEN_DATABASE_URLis required because PgBouncer in transaction mode does not support PostgresLISTEN. It should connect directly to the database rather than the pooler port.
Cloud Control-Plane API
When FUSEKIT_MODE=cloud is active, the following API endpoints are registered for tenant onboarding and management:
GET /api/cloud/config: Public configuration endpoint (used by the admin console to detect cloud mode).GET /api/orgs/POST /api/orgs: List user's organizations and create new ones.GET/POST/DELETE /api/orgs/{org_slug}/deploy-tokens: List, create, and revoke deploy tokens.GET/POST/PATCH/DELETE /api/orgs/{org_slug}/members: List and manage members (owner, admin, member).POST /api/deploy/target: Resolve the workspace's runtime and mint a short-lived signed deploy token (CLI).GET /api/cp/jwks: Deploy-token verification keys for runtimes.POST /api/cp/runtimes/register/POST /api/cp/runtimes/heartbeat: Connected-runtime registration and health (authenticated byfrt_runtime tokens).GET/POST/DELETE /api/orgs/{org_slug}/runtimes: Manage BYOC runtime registrations.GET/POST/DELETE /api/orgs/{org_slug}/domainsandPOST .../domains/{id}/verify: Customer wildcard domains with_fusekit-verifyTXT verification.GET /internal/tls-allowed?domain=: Caddy on-demand TLS gate; approves only verified custom-domain hosts.
Custom Domains
Verified orgs can serve apps from their own wildcard domain. Add apps.acme.com in the console, create the _fusekit-verify.apps.acme.com TXT record, point *.apps.acme.com at the Fusekit edge with a CNAME, and apps resolve at https://expenses.apps.acme.com. Certificates are issued per host via Caddy on-demand TLS (deploy/Caddyfile.cloud).
Authentication on custom domains is transparent: the first HTML navigation bounces through https://fusekit.cloud/auth/gateway (one redirect; shows the login page if there is no fusekit.cloud session), returns to /__fusekit/callback on the app host with a 60-second single-hop signed code, and sets a first-party fusekit_app_session cookie (12h, Ed25519-signed, scoped to the customer base domain so one login covers all apps on it). Membership is re-checked on every request, so removing a member takes effect within seconds, not at session expiry. When the session expires, the next navigation silently re-runs the redirect. GET /__fusekit/logout clears the cookie. No SDK or app-code changes are required; Authorization: Bearer continues to work for non-browser clients.
CLI Cloud Support
Log in to Fusekit Cloud by passing the --cloud flag:
fusekit login --cloud
fusekit deploy ./dist --app expenses --workspace acmeThis will automatically configure the CLI to target https://fusekit.cloud and prompt for a developer token. --workspace is validated against the token's organization; deploys are routed to the workspace's runtime (shared cloud pool or a connected BYOC runtime) via POST /api/deploy/target.
Use the whoami command to inspect your organization role and plan limits:
fusekit whoami
# Output: deploy-token in organization Acme Inc. (acme) on https://fusekit.cloudObject Storage
Fusekit opens storage through Go CDK:
blob.OpenBucket(ctx, bucketURL)The binary includes the S3, GCS, and Azure drivers. MinIO and other S3-compatible services work with:
FUSEKIT_BLOB_BUCKET_URL=s3://fusekit-assets?endpoint=http://minio:9000&disableSSL=true&s3ForcePathStyle=trueFusekit normalizes the legacy disableSSL option to Go CDK's current disable_https parameter. Production examples:
# AWS S3
FUSEKIT_BLOB_BUCKET_URL=s3://customer-fusekit-bucket
# MinIO or another S3-compatible service
FUSEKIT_BLOB_BUCKET_URL=s3://fusekit-assets?endpoint=https://minio.customer.internal&s3ForcePathStyle=true
# Google Cloud Storage
FUSEKIT_BLOB_BUCKET_URL=gs://customer-fusekit-bucket
# Azure Blob
FUSEKIT_BLOB_BUCKET_URL=azblob://customer-fusekit-containerGCS and Azure use the same ObjectStore interface and are compiled into the binary, so switching clouds is a FUSEKIT_BLOB_BUCKET_URL change, not a rebuild.
All deployed assets and runtime files are stored under portable logical keys.
In self-hosted mode:
apps/{app_slug}/versions/{version_id}/...
files/{app_slug}/{file_id}/{filename}In cloud mode:
tenants/{org_id}/apps/{app_slug}/versions/{version_id}/...
tenants/{org_id}/files/{app_slug}/{file_id}/{filename}Fusekit never stores deployed bundles or uploaded files on local disk.
Browser SDK
Install from npm (or load from a CDN with import { fusekit } from "https://esm.sh/@fusekit-cloud/sdk"):
npm install @fusekit-cloud/sdkimport { fusekit } from "@fusekit-cloud/sdk";
const user = await fusekit.user();
const task = await fusekit.db.collection("tasks").create({
title: "Review invoices",
done: false,
});
await fusekit.db.collection("tasks").update(task.id, { done: true });
const uploaded = await fusekit.files.upload(file);
const response = await fusekit.ai.chat({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Summarize this" }],
});The SDK infers the app slug and tenant slug from the current hostname (handling both app.domain and app.tenant.domain automatically) and sends credentialed same-origin requests. Tests and non-browser clients can configure it explicitly:
fusekit.configure({
app: "expenses",
tenant: "acme", // Optional tenant override
baseUrl: "https://expenses.acme.apps.fusekit.cloud",
});Realtime
Fusekit supports server-mediated realtime record subscriptions in both self-hosted and cloud modes using PostgreSQL LISTEN/NOTIFY and Server-Sent Events (SSE).
How it works
- An
AFTER INSERT OR UPDATE OR DELETEtrigger on therecordstable callspg_notify('fusekit_records', payload). - The payload contains only the record ID, collection, organization, and action type (to respect the 8KB payload limit).
- The Fusekit Server runs a background listener hub (
PostgresHub) onFUSEKIT_LISTEN_DATABASE_URL. - When a browser client subscribes, it opens an EventSource connection at
GET /runtime/{app}/db/{collection}/subscribe. - The server fans out notifications to subscribers, and the SDK automatically refetches updated records before invoking user handlers.
SDK Usage
To subscribe to realtime collection events:
const unsubscribe = fusekit.db.collection("tasks").subscribe((change) => {
if (change.op === "insert") {
console.log("New task created:", change.record);
} else if (change.op === "update") {
console.log("Task updated:", change.record);
} else if (change.op === "delete") {
console.log("Task deleted, ID:", change.recordId);
}
}, {
events: ["insert", "update", "delete"] // Optional: filter events
});
// To stop listening:
unsubscribe();Broadcast and presence
App-scoped rooms ride the same Postgres NOTIFY bus (POST
/runtime/{app}/realtime/broadcast + SSE at GET
/runtime/{app}/realtime/subscribe?room=), so they work identically in every
placement. Payloads are capped at 4KB. Channels are structured server-side
(app + kind + room); cross-app channels cannot be expressed.
const room = fusekit.realtime.room("document-123");
const stop = room.subscribe((message) => console.log(message.event, message.payload, message.sender));
await room.broadcast("cursor.move", { x: 10, y: 20 });
const presence = fusekit.realtime.presence("document-123", (members) => {
console.log("online:", members.map((member) => member.sender));
});
presence.leave();AI
AI is disabled by default:
FUSEKIT_AI_ENABLED=falseEnable the optional Compose profile and OpenAI-compatible LiteLLM proxy:
docker compose -f deploy/docker-compose.yml --profile ai up --buildSet FUSEKIT_AI_API_KEY if LiteLLM requires a master key. Provider credentials remain server-side. Fusekit records returned token usage in Postgres.
Security Notes
The full security brief — trust model, HTTP posture, fail-closed defaults, and
vulnerability reporting — is in SECURITY.md. Highlights:
- Every response carries baseline hardening headers (
X-Content-Type-Options: nosniff,X-Frame-Options,Referrer-Policy,Cross-Origin-Opener-Policy, a per-surface CSP, and HSTS when the public scheme is HTTPS) with zero tuning — seeinternal/httpapi/security.go. - CORS is closed by default: the SDK is same-origin, so no
Access-Control-Allow-Originheader is ever emitted and browsers block cross-origin reads. - In production self-hosted mode the server fails closed on weak shared secrets:
FUSEKIT_INTERNAL_PROXY_SECRETandFUSEKIT_DEPLOY_TOKENmust be at least 16 characters. - Shared secrets are compared in constant time; tokens, keys, and connection strings are never logged or returned.
- Do not expose
fusekit-serverdirectly. Only Caddy should receive public or employee traffic. - Browser identity headers are trusted only when the internal proxy secret is valid.
- Caddy removes incoming identity and proxy-secret headers before authentication.
- Runtime API app slugs must match the request subdomain, preventing cross-app data access.
- Browser admin APIs require configured SSO group membership.
- CLI deploy/admin APIs use a static bearer token for the MVP. Replace this with OIDC device authorization later.
- Deployment archives reject absolute paths, traversal, symlinks, hardlinks, special files, excessive file counts, and excessive expanded size.
- App assets and uploaded files live only in the configured Go CDK bucket.
- Keep database, MinIO, OAuth2 Proxy, and Fusekit Server on private networks.
- Rotate
FUSEKIT_INTERNAL_PROXY_SECRET,FUSEKIT_DEPLOY_TOKEN, database credentials, MinIO credentials, and OAuth cookie secrets before production use.
Validation
go test ./...
go vet ./...
npm test
npm run build
docker compose -f deploy/docker-compose.yml config
docker compose -f deploy/docker-compose.yml up --buildOperations
Generated installations include backup and restore scripts, exposed through the CLI:
fusekit backup --install-dir /opt/fusekit
fusekit restore --install-dir /opt/fusekit /secure/backups/20260611T120000ZBackups contain a PostgreSQL custom-format dump, the complete object bucket, and installation configuration.
Upgrades always take a backup first:
fusekit upgrade --install-dir /opt/fusekit --version v0.2.0The command updates the image tag, creates a timestamped pre-upgrade backup, pulls images, runs the embedded database migrations during startup, and recreates services.
The Go tests cover authentication, wildcard-domain parsing, cross-app runtime rejection, cache policy, tar traversal and link rejection, and the Go CDK object lifecycle. Compose provides the Postgres and MinIO dependencies for end-to-end deployment and runtime checks.
API conformance smoke test
examples/api-conformance/ is a single deployable app that exercises every
feature API the SDK exposes — identity (fusekit.user()), document CRUD,
database realtime subscriptions, file storage, AI chat, and realtime
broadcast/presence — and reports per-API pass/warn/fail with the assertions and
latency for each step. Deploy it to any tenant to confirm a release behaves on
that tenant:
npm run build --workspace @fusekit-cloud/api-conformance
fusekit deploy examples/api-conformance/dist --app conformance --workspace <tenant>Open the app and click Run all checks. Green means the feature works on that
tenant; warnings flag optional features that are disabled (e.g. AI). Each check
writes only to throwaway __conformance* collections / files and cleans up
after itself. For local testing against a remote tenant without deploying, pass
?app=&tenant=&baseUrl= query params to point the SDK at a host.
MVP Boundaries
Not included:
- BYOC Provisioning: Connected BYOC runtimes register and heartbeat against the control plane, but Fusekit does not provision infrastructure into customer cloud accounts (Terraform/CloudFormation); customers run the existing Compose bundle.
- Postgres Row-Level Security (RLS): Scoping is currently handled at the query layer via
org_idparameters. RLS will be added in a future phase to unlock direct browser-to-Supabase Realtime connectivity. - Billing & Payments: The database contains a
planstable to enforce limits, but integration with a billing provider (e.g., Stripe) is out of scope. - Other: Kubernetes operator, KOTS or air-gap packaging, direct-to-bucket uploads, STS credential vending, or complex RBAC.
