GUIDES

Self-hosting Agentifys

What a self-hosted Agentifys deployment involves, what it depends on, and how to get one set up.

This page describes the deployment as it exists, not a self-service install. There is no published container image and no public source download, so you cannot stand one up from this site alone. The material below is here so you can evaluate the requirements honestly before you talk to us. To actually deploy, contact us.

The shape of an install

A deployment is two containers on one host, plus a managed Postgres. There is no database container. The backend is not exposed directly: it listens on port 8001 on the internal network, and Caddy is the only thing bound to the public interface.

FieldTypeDescription
webcontainerCaddy on :80 and :443. Terminates TLS, serves the built console and the pre-rendered public site, and reverse-proxies /v1, /api, /admin, /static, /ws, /oauth and /health to the backend.
backendcontainerFastAPI on :8001, internal only. The agent runtime, the public /v1 API, the widget script at /static/widget.js, and the embedding and reranking models.
Postgres + pgvectorexternalManaged Supabase. Holds projects, sessions, logs, encrypted model keys and vault credentials, and the embedding vectors for RAG. It also provides the auth layer.

Retrieval runs entirely inside the backend container. Embeddings come from BAAI/bge-m3 and reranking from a local cross-encoder, both loaded into memory at startup, so no document text is sent to an embedding API. That is also what makes the backend heavy.

FieldTypeDescription
Memoryrequired6 GBThe compose file sets a 6 GB limit and a 4 GB reservation. The models plus torch are roughly 2.3 GB resident; below this the backend is killed under load.
Domain + DNSrequiredrequiredA real domain with an A/AAAA record pointing at the host before the first start. Caddy issues a Let’s Encrypt certificate on boot and needs inbound ports 80 and 443 open.
CPU architecturematchBuild on the same architecture as the host. An amd64 image on an arm64 host runs under emulation and is unusably slow.
First build~3 GBThe model weights are baked into the image, so the first build is slow and the image is large.
Readiness is gated on the model pre-warm. The backend health check allows a 180-second start period and the web container will not start until the backend reports healthy, so a cold start takes a minute or two.

The database is not optional

Agentifys is a Postgres application. DATABASE_URL must point at a Postgres database with the vector extension available. On first boot the backend runs CREATE EXTENSION IF NOT EXISTS vector and then its own migrations, so you do not apply a schema by hand.

FieldTypeDescription
RolerequiredpostgresThe connection must use the postgres superuser role. It needs CREATE EXTENSION vector, and read access to auth.users so team invites can bind to a verified account.
Endpointrequiredsession poolerOn Supabase, use the session pooler string (IPv4, port 5432). Not the direct endpoint, which is IPv6-only without a paid add-on, and not the transaction pooler on 6543.
Extensionrequiredvectorpgvector. Enable it explicitly in the provider console as well, rather than relying on first-boot creation.
If DATABASE_URL is unset the backend does not refuse to start — it falls back to a local SQLite and ChromaDB development mode that is single-tenant and has no auth. In that mode the multi-tenant paths return nothing, including the one that loads a project's provider keys, so every chat request answers PROJECT_NO_API_KEY. It looks like it booted. It is not a deployment.

Authentication

Console logins are Supabase Auth. The backend verifies HS256 tokens only, against the project's legacy JWT secret. If the Supabase project's active signing key is asymmetric, every admin login returns 401 — the fix is to use the legacy HS256 secret.

The server refuses to boot on a missing or placeholder JWT secret. If SUPABASE_JWT_SECRET is empty or still set to the local-dev placeholder super-secret-jwt-token-with-at-least-32-characters-long, the process prints a fatal message and exits, because anyone could otherwise forge a token for any account. The one exception is when SUPABASE_URL points at 127.0.0.1 or localhost, where it warns and continues for local development.

Enable Confirm email in the Supabase auth settings. Teammate invites only bind once the invitee's email is verified.

Environment

Seven values must be set before the first start. Five of them are read by the backend at runtime; the two VITE_ values are build arguments baked into the console bundle, so changing either means rebuilding the web image.

FieldTypeDescription
SITE_ADDRESSrequiredhostnameThe public domain Caddy serves and requests a certificate for, e.g. agent.yourdomain.com. Comma-separate to serve several names from one deployment — Caddy issues a certificate for each. If you do, set PUBLIC_ALIASES too.
DATABASE_URLrequiredstringPostgres connection string, postgres role, session pooler, against a database with pgvector available.
SUPABASE_URLrequiredurlThe Supabase project URL. A localhost value puts the server into permissive dev behaviour, so in production this must be the real URL.
SUPABASE_JWT_SECRETrequiredstringThe legacy HS256 JWT secret. Missing or placeholder values stop the process at startup.
ENCRYPTION_KEYrequiredstringThe Fernet key that encrypts stored model keys and vault credentials. See below — it has a silent fallback, and it cannot be rotated.
VITE_SUPABASE_URLrequiredbuild argSame Supabase URL, compiled into the console bundle.
VITE_SUPABASE_ANON_KEYrequiredbuild argThe Supabase anon public key, compiled into the console bundle.

Everything else is optional and has a working default.

FieldTypeDescription
PUBLIC_BASE_URLurlPublic HTTPS base URL of this deployment, no trailing slash. Fixes the MCP OAuth redirect at <PUBLIC_BASE_URL>/oauth/mcp/callback. Unset means OAuth-protected MCP servers cannot be connected.
PUBLIC_ALIASEShostnamesEvery OTHER hostname this deployment answers on, comma-separated, when SITE_ADDRESS lists more than one. PUBLIC_BASE_URL names the canonical host and holds one value, so without this the other names are not recognised as this server — and the guard that stops a project registering our own /mcp as its MCP server stops covering them. Renaming a deployment means moving the old hostname in here, not deleting it.
INDEXING_ENABLEDbooleanDefault true. Set to false to pause document upload and indexing — the heavy embedding path — while search over already-indexed documents keeps working.
EERRAA_EXAMPLE_PROJECTbooleanDefault true. Creates one example project for each new organisation.
EERRAA_SEED_DEMOSbooleanDefault off. Seeds illustrative demo projects on boot. They carry fake vertical data and no organisation, so leave this off in production.
EERRAA_ALLOW_LOCALHOST_WEBHOOKSbooleanDefault off. Off blocks webhook tools from calling loopback and private addresses. Turning it on removes that guard, so keep it off outside local development.
SMTP_HOSTstringEnables the one outbound email Agentifys sends, a webhook-failure notice. With SMTP_HOST unset, no email leaves the server. Project alerts are in-app regardless.
SMTP_PORTintegerDefault 587. STARTTLS is used when the port is exactly 587.
SMTP_USERstringLogin and From address. Also the fallback recipient.
SMTP_PASSWORDstringSMTP password or app password.
ALERT_EMAIL_TOstringRecipient for the webhook-failure notice. Falls back to SMTP_USER.
Do not set ANTHROPIC_API_KEY or OPENAI_API_KEY on the server. The backend deletes both from its own environment at startup so no code path can fall back to a shared platform key. Provider keys belong to a project and are added in the console — see the model keys guide.

ENCRYPTION_KEY

ENCRYPTION_KEY is the Fernet key that encrypts every stored provider key and every per-user vault credential. Generate it once, before the first start:

bash
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

It must be set explicitly, because the fallback chain is silent. The value is resolved like this:

FieldTypeDescription
Exactly 44 charactersused as-isThe value is treated as a Fernet key directly. This is what the generator above produces, and it is the only intended case.
Any other lengthcoercedThe first 32 bytes are taken, right-padded with the character 0 to 32 bytes, and base64-encoded. A longer passphrase silently contributes only its first 32 bytes.
UnsetderivedThe key becomes SHA-256 of SUPABASE_JWT_SECRET — so your secrets at rest are tied to your JWT secret, and rotating that JWT secret makes every stored key undecryptable.
Unset, with no JWT secretconstantThe key is derived from the literal string dev-fallback-secret, which is in the source. Encryption at rest is then effectively decorative. Production cannot reach this state, because a missing JWT secret already stops the server.
There is no key rotation. Nothing re-encrypts existing ciphertext, so changing ENCRYPTION_KEY after go-live does not migrate anything — it makes every stored provider key and vault credential permanently undecryptable, and the only recovery is to re-enter all of them by hand. Treat it as a root secret: generate it once, back it up outside the host, and never change it.

What self-hosting does and does not include

Running Agentifys on your own host means the agent runtime, your documents, your embeddings, your logs, and your encrypted credentials sit on infrastructure you control, and retrieval never leaves it. Model calls still go to whichever provider you gave a key to, billed on your account, so you keep that relationship too.

The deployment described above is not fully air-gapped. It depends on managed Supabase for Postgres and for the auth layer, which means your data lives in your Supabase project rather than on your host. If your requirement is that nothing leaves your own network, that is a different build and a conversation to have up front rather than after a deploy.

A production rollout usually needs things around it as well: SSO, a DPA, an SLA, a security review, and named support. Those are agreements, not environment variables.

Want to run Agentifys on your own infrastructure, or work out whether it fits your constraints? Contact us and we will go through it with you.