MAIA runs on DigitalOcean infrastructure. A single DigitalOcean API Token (DIGITALOCEAN_TOKEN) serves as the master secret from which most other credentials and access tokens are derived.
The DO token is used directly for all DO API calls (App Platform, Droplets, Spaces management, GenAI agents, knowledge bases, serverless inference). Several internal secrets are derived from it using HMAC-SHA256, so they never need to be stored as separate environment variables:
| Derived Value | Derivation | Purpose |
|---|---|---|
| CouchDB admin password | HMAC-SHA256(token, 'maia-couchdb-admin') base64url, first 32 chars |
Authenticates to the CouchDB database |
| Session secret | HMAC-SHA256(token, 'maia-session-secret') base64url, first 32 chars |
Signs Express session cookies |
| Admin passphrase | DO token used directly | Pasted once at first admin login, then passkey takes over |
| DO Inference Model Access Key | Created via POST /v2/gen-ai/models/api_keys, cached in CouchDB |
Authenticates chat requests to all public AI providers |
The CouchDB username and admin username are both hard-coded to admin.
Token rotation warning: If the DO token is rotated, the derived CouchDB password changes but the CouchDB droplet still has the old one. Before or after rotating, update the CouchDB admin password via the CouchDB config API (PUT /_node/_local/_config/admins/admin) or via SSH to the droplet. Session secret rotation simply logs out all users (they re-authenticate with passkeys). The cached inference key in CouchDB (maia_config/do_inference_key) will also need to be deleted so a new one is created on next startup.
The application runs as a single DigitalOcean App Platform web service:
- Runtime: Node.js (Express.js backend + Vue 3 SPA frontend)
- Build:
npx vite buildproduces thedist/folder; Express serves it as static files in production - Port: App Platform sets
PORTautomatically; defaults to3001locally - HTTPS: App Platform terminates TLS at its load balancer and proxies to the app over HTTP. The app sets
trust proxywhenPUBLIC_APP_URLstarts withhttps://so secure cookies work correctly behind the reverse proxy. - Secure cookies: Enabled automatically when
PUBLIC_APP_URLstarts withhttps://
When PUBLIC_APP_URL starts with https:// (i.e., a cloud deployment), the server automatically provisions a DigitalOcean Droplet running CouchDB 3 in Docker:
- Droplet:
s-1vcpu-1gbUbuntu 22.04 intor1region - Container:
couchdb:3with persistent volumes - Port: 5984
- Credentials:
admin/ derived password (see token derivations above) - Startup: The server retries the CouchDB connection up to 10 times at 30-second intervals while the droplet warms up
Backups / snapshots (required once Groups exist). Most secrets are
derived from the DO token and most user data can be restored from
patient-side backups, but maia_groups is the exception: each group's
private signing key is random entropy that exists only in that database
(deliberately not token-derived — see Documentation/Groups.md §6.7). If
the CouchDB droplet is lost without a backup, every group it hosts becomes
unrecoverable within 24 hours (membership credentials stop refreshing).
Enable DigitalOcean Droplet snapshots (or equivalent) on the CouchDB
droplet, and ensure each group's admin has downloaded its recovery kit
(admin page → Patient Groups → key icon), which is the off-server escrow of
the group's key material.
For local development, CouchDB runs in a Docker container:
docker run -d --name couchdb -p 5984:5984 \
-e COUCHDB_USER=admin -e COUCHDB_PASSWORD=adminpass \
couchdb:3
- URL:
http://localhost:5984 - Credentials:
admin/adminpass(hard-coded defaults when no DO token is present)
| Database | Purpose |
|---|---|
maia_sessions |
Express session store |
maia_users |
User documents (profile, agent info, KB status, indexing state) |
maia_audit_log |
Audit trail |
maia_chats |
Saved chat conversations |
maia_config |
Server configuration (cached DO Inference key, OpenSearch database ID) |
A managed DigitalOcean OpenSearch cluster provides vector search for knowledge base queries:
- Provisioning: Automatically discovered or created via the DO API at first KB creation. One cluster per account — the server enforces this by checking existing clusters before creating.
- Access: The database UUID is resolved via the DO API (
GET /v2/databases?engine=opensearch), cached in CouchDB (maia_config/opensearch_database_id), and used for KB creation via the DO GenAI endpoints. - Usage: Agents query the OpenSearch-backed knowledge base when answering user questions
- Legacy: The
OPENSEARCH_URLenv var is still supported as a fallback but is no longer required.
Each user gets a DigitalOcean GenAI Agent provisioned during setup:
- Model:
openai-gpt-oss-120b(DO-hosted open-source model, selected viaDO_MODEL_IDenv var or auto-detected from the/v2/gen-ai/modelsAPI) - Endpoint: Each agent has a unique endpoint (e.g.,
https://<id>.agents.do-ai.run/api/v1) - API Key: Per-user agent API keys are created and stored in the user's CouchDB document
- Knowledge Base: Each agent is attached to a user-specific knowledge base for RAG (retrieval-augmented generation)
File storage uses a DigitalOcean Spaces bucket (S3-compatible):
- Bucket name:
maia - Authentication:
SPACES_AWS_ACCESS_KEY_IDandSPACES_AWS_SECRET_ACCESS_KEY(S3-compatible credentials, cannot be derived from the DO token because Spaces uses the separate AWS S3 API)
Each user has three tiers of file storage within the bucket:
| Folder | Path Pattern | Purpose |
|---|---|---|
| Root | {userId}/filename |
Temporary holding area for newly uploaded files |
| Archived | {userId}/archived/filename |
Long-term storage; files preserved but not indexed |
| KB | {userId}/{kbName}/filename |
Files uploaded to the knowledge base for AI indexing |
.keepplaceholder files are created to ensure empty directories exist- Files are moved between folders as their status changes (e.g., root to archived after processing, or root to KB for indexing)
Each user's knowledge base is a DigitalOcean GenAI Knowledge Base:
- Embedding model:
gte-large-en-v1.5(resolved from the/v2/gen-ai/models?usecases=MODEL_USECASE_KNOWLEDGEBASEAPI, or overridden withDO_EMBEDDING_MODEL_ID) - Backed by: The shared OpenSearch cluster
- Indexing: Files in the KB folder are indexed asynchronously; the server polls the DO API for completion
- Status tracking: Indexing status (
phase,progress,indexed files) is persisted in the user's CouchDB document
When individual provider API keys are not set, MAIA routes public AI chat through DigitalOcean Serverless Inference at https://inference.do-ai.run/v1:
- Authentication: A Model Access Key is created automatically from the DO token at startup and cached in CouchDB
- API format: OpenAI-compatible (
/v1/chat/completions) - Billing: All usage billed through the single DO account
| Provider | Model ID on DO Inference | Default for |
|---|---|---|
| Anthropic Claude | anthropic-claude-4.6-sonnet |
anthropic provider |
| OpenAI GPT | openai-gpt-4o |
openai provider |
| DeepSeek | deepseek-r1-distill-llama-70b |
deepseek provider |
Google Gemini is not available on DO Inference. To use Gemini, set GEMINI_API_KEY as a separate environment variable with a key obtained directly from Google AI Studio. When set, Gemini appears as an additional chat provider.
If an individual provider key is set (e.g., ANTHROPIC_API_KEY), it takes precedence over DO Inference for that provider. This is useful for local development or if you prefer direct billing with a specific provider.
Only these variables are needed in production:
| Variable | Purpose |
|---|---|
PUBLIC_APP_URL |
The public URL (e.g., https://test.agropper.xyz). Determines secure cookies, trust proxy, and passkey origin. |
DIGITALOCEAN_TOKEN |
Master secret for all DO API calls and derived credentials. Also used to auto-discover/create the OpenSearch cluster. |
SPACES_AWS_ACCESS_KEY_ID |
S3-compatible access key for DO Spaces. |
SPACES_AWS_SECRET_ACCESS_KEY |
S3-compatible secret key for DO Spaces. |
| Variable | Typical Value | Purpose |
|---|---|---|
PUBLIC_APP_URL |
http://localhost:5173 |
Local Vite dev server URL |
CLOUDANT_URL |
http://localhost:5984 |
Local Docker CouchDB |
DIGITALOCEAN_TOKEN |
dop_v1_... |
Required for DO API calls (agents, spaces, inference) |
SPACES_AWS_ACCESS_KEY_ID |
DO00... |
Same as production |
SPACES_AWS_SECRET_ACCESS_KEY |
f1Ru... |
Same as production |
PUBLIC_APP_URL:http://localhost:5173locally vshttps://...in production. Controls secure cookies, trust proxy, and passkey configuration.CLOUDANT_URL: Points to local Docker CouchDB (localhost:5984). In production, the CouchDB droplet URL is set automatically by the provisioning code.PORT: Not needed as an env var. Defaults to 3001 locally; App Platform sets it automatically.
DigitalOcean Spaces uses the S3-compatible API, which requires its own access key and secret key pair. These are generated separately in the DO control panel (API > Spaces Keys) and are not related to the main DO API token. There is no API to create Spaces keys programmatically from the DO token.
Each maia-setup-log.pdf file includes the following environment information at the top:
| Field | Source |
|---|---|
| Generated | Current date and time |
| Version | App version from package.json |
| User | User ID |
| App URL | window.location.origin |
| Folder | Local folder name |
| Browser | Browser name, version, and OS (parsed from user agent) |
| Chat providers | Available providers with their model names (e.g., "DigitalOcean (openai-gpt-oss-120b), Anthropic (claude-opus-4-6)") |
This information helps diagnose issues by capturing the exact environment at the time of setup.