Self-host quickstart
This runs Function Client on one machine with Docker: the server, its databases, object storage and the web client. When it's up, your team opens one address in a browser.
You need:
- Docker with Compose 2.20 or newer (
docker compose version): Docker Desktop, orcolimaon a Mac. Give Docker at least 4 GB of memory. - An Anthropic API key, from console.anthropic.com. The assistant runs on it.
- To serve a team, a machine they can reach and a domain whose DNS you control. On a laptop, neither.
- On Windows, WSL 2. Every command here is bash: run them in a WSL shell
(Ubuntu), with Docker Desktop's WSL integration turned on for that
distribution, and clone into the WSL filesystem (
~/client, not under/mnt/c). Under/mnt/c,chmod 600silently does nothing, so the settings file would stay readable by everyone, and builds are slow. Docker's memory there is WSL's, set in%UserProfile%\.wslconfig([wsl2]thenmemory=4GB).
Sign-in uses this server's own email-and-password accounts, so there is no third-party account to set up. Sign in with Privy covers signing in with Privy instead.
1. Get the code and the settings file
.env.selfhost holds every secret the server has. Only you should be able
to read it. It is listed in .gitignore; keep it that way.
2. Fill in the required block
Open .env.selfhost. Fill the empty values in the block marked
required to boot:
| Name | What to put there |
|---|---|
GRAPH_PASS | openssl rand -hex 16. Also Neo4j's password, so at least 8 characters. |
JWT_SECRET | openssl rand -hex 32. Every login is signed with it, so at least 32 characters. |
ENCRYPTION_MASTER_KEY | openssl rand -hex 32. Exactly 64 hex characters. It encrypts every connected integration's credentials: back it up (Backup and restore), and never change it. |
INTERNAL_REQUEST_SECRET | openssl rand -hex 32 |
ANTHROPIC_API_KEY | your Anthropic API key |
Leave the rest of that block as it is. Compose sets the database, graph, Redis and storage addresses itself.
Then add these lines to the file:
Replace each <…> with the command's output.
On a server your team reaches
Point DNS for your domain and for pages.<your domain> at the machine.
Pages that people publish run on that second host, so they can never read
the app's session. Then add:
Step 4 makes the server public with EDGE_BIND=0.0.0.0, once you have your
account. The edge container then obtains the certificates itself, so ports
80 and 443 must be open to the internet. Never set EDGE_BIND=0.0.0.0
without FUNCTION_SCHEME=https: passwords would cross the network in the
clear.
On a laptop, skip this. The address is http://localhost.
3. Start it
The first build takes several minutes. Pass the same --env-file to every
later compose command (ps, logs, down), or compose stops, saying
GRAPH_PASS is unset.
To follow the server as it starts:
The boot log prints one mirra.<subsystem>=<mode> line for each subsystem:
mirra.auth=local, mirra.email=none, mirra.billing=none and so on. If
a required value is missing, the server exits and names every missing value
at once. Some warnings are expected, one for each thing this setup leaves
off: PUBLIC_PAGES_HOST is unset (see
Reverse proxy),
mirra.script_runtime=unavailable until you turn on flows,
and Agora, push notifications, OPENAI_API_KEY and GOOGLE_TENOR_KEY not
set.
4. Make yourself the admin
The first account on the server is its admin, and until one exists, whoever
reaches the address first can take it. On a laptop, open http://localhost,
choose Create an account, and go on to step 5.
On a server, a new domain is public as soon as its certificate is issued, so
create the account before the edge listens to the internet. On the machine
itself, run the lines below, with your email in place of you@example.com.
At the blank prompt, type your password (8 or more characters, no double
quote or backslash) and press Enter.
It prints "role":"admin" when your account exists. (If you set
FUNCTION_PORT, use that port instead of 3000.)
Then add EDGE_BIND=0.0.0.0 to .env.selfhost and run
docker compose --env-file .env.selfhost up -d. Sign in at
https://<FUNCTION_DOMAIN>. If registering ever answers Sign-up is closed on this server before you have an account, someone else got there first: run
down -v and start again.
5. Send the link to your team
Sign-up closes after the first account, so open it while your team joins.
Add this line to .env.selfhost:
Then run up again. Compose recreates the server, because its settings
changed, and leaves the rest running:
Send your team the address. Each person opens it and creates an account.
Once everyone has joined, set LOCAL_SIGNUP=off and run up -d
again. While sign-up is open, anyone who can reach the address can make an
account and spend your Anthropic key: with billing off (the default),
nothing limits what an account spends.
Your team uses the client in the browser from here. To serve the client from a CDN of your own as well, see Getting the client to your team.
Turn on flows
Flows and scripts are off: deploying one answers 503. The built-in flows
(scheduled flows, batch operations) need them. To turn them on, add this
line to .env.selfhost and run up -d:
Each script then runs as a child process inside the server container, with
network access and the server's own OS user. Anyone who can deploy a
script can take over the server: read every value in .env.selfhost
(JWT_SECRET and ENCRYPTION_MASTER_KEY included), read and change every
account's data in the databases directly, and rewrite other people's
scripts. Turn this on only if everyone who can sign in is someone you would
hand the server to, and never while LOCAL_SIGNUP=open: set
LOCAL_SIGNUP=off first. Under Privy sign-in, anyone who can sign in to
your Privy app gets an account, so restrict sign-in in the Privy dashboard
first (Sign in with Privy).
To let someone join later, remove SCRIPT_RUNTIME=local, set
LOCAL_SIGNUP=open and run up -d; once they have an account, set
LOCAL_SIGNUP=off, put SCRIPT_RUNTIME=local back and run up -d again.
Flows don't run in between.
Call reminders and call summaries deploy too. They run during and after voice calls, and without live-call transcription (Voice calls) a reminder posts "No conversation detected" and a summary is skipped. The server's image already carries the SDK that scripts load.
Everything else is optional
apps/server/.env.selfhost.example describes each optional setting beside
its commented-out line: email (SMTP), Stripe billing, the event bus,
organizations, the crypto bundle, video rendering. Uncomment what you want,
then run up -d.
- Connect Google, Jira, X, Trello, Shopify, GitHub or Telegram: Connecting integrations.
- Put your own proxy in front, or split the API and the client: Reverse proxy.
- Back up and restore: Backup and restore.
- Voice calls: Voice calls.
- Other ways to get the client to your team: Getting the client to your team.
- Licenses and caveats of the software Function Client depends on: Third-party software: licenses and caveats.
Stop, update, remove
Back up before an update (Backup and restore).