Configuration¶
Imbi uses pydantic-settings to load configuration from three sources, merged in priority order:
- Environment variables — always win
config.tomlfiles — discovered in this order, first match wins per key:./config.toml(project root)~/.config/imbi/config.toml(user)/etc/imbi/config.toml(system)
- Built-in defaults — sensible defaults for development
A .env file in the working directory is also read for environment variables (pydantic-settings default).
Quick Start¶
Development Setup¶
just serve runs just docker first, which boots all services and writes a fresh .env with the dynamically-allocated host ports (Postgres, ClickHouse, Valkey, LocalStack, Mailpit, Jaeger) and freshly-generated JWT and Fernet secrets if none are present.
Production Setup¶
Put structural configuration in /etc/imbi/config.toml and inject secrets via environment variables:
sudo install -d /etc/imbi
sudo $EDITOR /etc/imbi/config.toml
export POSTGRES_URL='postgresql://imbi:...@db.example.com:5432/imbi'
export IMBI_AUTH_JWT_SECRET="$(secrets-manager get jwt-secret)"
export IMBI_AUTH_ENCRYPTION_KEY="$(secrets-manager get encryption-key)"
Generate the JWT secret and Fernet encryption key with:
python -c "import secrets; print(secrets.token_urlsafe(32))"
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
Stable secrets in production
If IMBI_AUTH_JWT_SECRET or IMBI_AUTH_ENCRYPTION_KEY is unset, the application auto-generates a value at startup and logs a warning. This is fine for ephemeral dev runs, but in production it will invalidate every issued token and break every encrypted credential on every restart — always provide both explicitly.
Application Settings¶
Server (IMBI_API_*)¶
Configured by ServerConfig. The [server] section in config.toml maps to these.
| Variable | Default | Description |
|---|---|---|
ENVIRONMENT |
development |
Deployment environment (development, staging, production). Unprefixed — picks up whatever the platform (Vercel, ECS, GHA, etc.) already exports. Currently drives Mailpit dev auto-config. |
IMBI_API_HOST |
localhost |
Server bind address |
IMBI_API_PORT |
8000 |
Server bind port. Strings of the form tcp://ip:port (injected by Kubernetes service discovery) are parsed and the port extracted, so the <SERVICE>_PORT=tcp://… pattern does not collide with this field. |
IMBI_API_CORS_ALLOWED_ORIGINS |
[] |
JSON array of allowed CORS origins. Credentials and the Authorization header are allowed for cross-origin requests from these origins. Also the allow-list of trusted hosts for per-request OAuth URL derivation in multi-host deployments (see "OAuth2 Authorization Server"). |
IMBI_API_FORWARDED_ALLOW_IPS |
'' |
Comma-separated list (or *) of trusted proxy IPs whose X-Forwarded-* headers are honored. Required when running behind a reverse proxy so rate limiting keys on the real client IP. Empty disables the middleware. |
IMBI_API_URL |
'' |
Public URL where the API is reachable from a browser, including any path prefix it is mounted under (e.g. https://imbi.example.com/api). Drives FastAPI route mounting, hypermedia links, and OAuth redirect URIs. Falls back to http://{host}:{port} (no prefix) for dev loopback. /docs and /openapi.json are always served at the root regardless of prefix. |
PostgreSQL + Apache AGE (POSTGRES_*)¶
PostgreSQL with the Apache AGE extension stores all graph and relational domain data (organizations, teams, projects, users, permissions, blueprints, etc.). The [postgres] section in config.toml maps to these.
| Variable | Default | Description |
|---|---|---|
POSTGRES_URL |
postgresql://postgres:secret@localhost:5432/imbi |
PostgreSQL DSN |
POSTGRES_GRAPH_NAME |
imbi |
Apache AGE graph name |
POSTGRES_MIN_POOL_SIZE |
2 |
Minimum connection pool size |
POSTGRES_MAX_POOL_SIZE |
10 |
Maximum connection pool size |
ClickHouse (CLICKHOUSE_*)¶
ClickHouse stores analytics, operations logs, audit logs, email send logs, and API-key usage metrics. The [clickhouse] section in config.toml maps to these.
| Variable | Default | Description |
|---|---|---|
CLICKHOUSE_URL |
clickhouse+http://localhost:8123 |
ClickHouse DSN. Credentials in the URL are honored. |
CLICKHOUSE_CONNECT_TIMEOUT |
10.0 |
Connection timeout in seconds |
CLICKHOUSE_MAX_CONNECT_ATTEMPTS |
10 |
Maximum connection attempts on startup |
Valkey (VALKEY_*)¶
Valkey (Redis-compatible) is used for ephemeral caching. The [valkey] section in config.toml maps to these.
| Variable | Default | Description |
|---|---|---|
VALKEY_URL |
valkey://localhost:6379/0 |
Valkey DSN (defaults: host localhost, port 6379, db 0) |
Object Storage (S3_*)¶
S3-compatible storage holds icons, avatars, and document uploads. The [storage] section in config.toml maps to these.
| Variable | Default | Description |
|---|---|---|
S3_ENDPOINT_URL |
(none — real AWS S3) | Set to a LocalStack or MinIO endpoint to override |
S3_ACCESS_KEY |
(none) | Access key ID. Omit to use the default AWS credential chain (instance role, profile, etc.). |
S3_SECRET_KEY |
(none) | Secret access key |
S3_BUCKET |
imbi-uploads |
Bucket name |
S3_REGION |
us-east-1 |
AWS region |
S3_CREATE_BUCKET_ON_INIT |
true |
Auto-create the bucket on startup if it does not exist |
S3_MAX_FILE_SIZE |
52428800 |
Upload size limit in bytes (50 MiB default) |
S3_ALLOWED_CONTENT_TYPES |
image/jpeg, image/png, image/gif, image/webp, image/svg+xml, application/pdf | JSON array of MIME types accepted for upload |
S3_THUMBNAIL_MAX_SIZE |
256 |
Max thumbnail dimension in pixels (aspect ratio preserved) |
S3_THUMBNAIL_QUALITY |
85 |
WEBP quality for generated thumbnails (0–100) |
Email (IMBI_EMAIL_*)¶
SMTP-based transactional email (password reset, welcome, email verification, security alerts). The [email] section in config.toml maps to these.
| Variable | Default | Description |
|---|---|---|
IMBI_EMAIL_ENABLED |
true |
Master switch — disable to no-op all sends |
IMBI_EMAIL_DRY_RUN |
false |
Build and log messages without sending |
IMBI_EMAIL_SMTP_HOST |
localhost |
SMTP server host |
IMBI_EMAIL_SMTP_PORT |
587 |
SMTP server port |
IMBI_EMAIL_SMTP_USE_TLS |
true |
Use STARTTLS |
IMBI_EMAIL_SMTP_USE_SSL |
false |
Use implicit TLS (SMTPS, typically port 465) |
IMBI_EMAIL_SMTP_USERNAME |
(none) | SMTP auth username |
IMBI_EMAIL_SMTP_PASSWORD |
(none) | SMTP auth password |
IMBI_EMAIL_SMTP_TIMEOUT |
30 |
SMTP socket timeout in seconds |
IMBI_EMAIL_FROM_EMAIL |
noreply@imbi.example.com |
From address |
IMBI_EMAIL_FROM_NAME |
Imbi |
From display name |
IMBI_EMAIL_REPLY_TO |
(none) | Optional Reply-To address |
IMBI_EMAIL_MAX_RETRIES |
3 |
Send retry attempts |
IMBI_EMAIL_INITIAL_RETRY_DELAY |
1.0 |
First retry delay in seconds |
IMBI_EMAIL_MAX_RETRY_DELAY |
60.0 |
Maximum backoff delay in seconds |
IMBI_EMAIL_RETRY_BACKOFF_FACTOR |
2.0 |
Exponential backoff multiplier |
Mailpit auto-config
When ENVIRONMENT=development (or unset) and IMBI_EMAIL_SMTP_HOST=localhost with the default port 587, the settings model checks for MAILPIT_SMTP_PORT (written by just docker) and substitutes it, disabling TLS unless IMBI_EMAIL_SMTP_USE_TLS is set explicitly. This makes development "just work" against Mailpit without manual SMTP wiring.
Authentication & Authorization (IMBI_AUTH_*)¶
The [auth] section in config.toml maps to these. Shared JWT/encryption settings come from imbi_common; the API service adds password policy, sessions, API keys, MFA, rate limits, and OAuth behavior.
JWT and Encryption¶
| Variable | Default | Description |
|---|---|---|
IMBI_AUTH_JWT_SECRET |
(auto-generated) | Secret used to sign JWT access and refresh tokens. Set explicitly in production — otherwise every restart invalidates all tokens. |
IMBI_AUTH_JWT_ALGORITHM |
HS256 |
JWT signing algorithm |
IMBI_AUTH_ACCESS_TOKEN_EXPIRE_SECONDS |
3600 |
Access token lifetime (1 hour) |
IMBI_AUTH_REFRESH_TOKEN_EXPIRE_SECONDS |
2592000 |
Refresh token lifetime (30 days). Refresh tokens rotate on every use. |
IMBI_AUTH_ENCRYPTION_KEY |
(auto-generated) | Base64-encoded Fernet key used to encrypt OAuth provider tokens and OAuth client secrets at rest. Set explicitly in production — otherwise every restart breaks all encrypted secrets. |
Password Policy¶
| Variable | Default | Description |
|---|---|---|
IMBI_AUTH_PASSWORD_MIN_LENGTH |
12 |
Minimum password length |
IMBI_AUTH_PASSWORD_REQUIRE_UPPERCASE |
true |
Require an uppercase letter |
IMBI_AUTH_PASSWORD_REQUIRE_LOWERCASE |
true |
Require a lowercase letter |
IMBI_AUTH_PASSWORD_REQUIRE_DIGIT |
true |
Require a digit |
IMBI_AUTH_PASSWORD_REQUIRE_SPECIAL |
true |
Require a special character |
Sessions and API Keys¶
| Variable | Default | Description |
|---|---|---|
IMBI_AUTH_SESSION_TIMEOUT_SECONDS |
86400 |
Session timeout (24 hours) |
IMBI_AUTH_MAX_CONCURRENT_SESSIONS |
5 |
Max concurrent sessions per user; oldest is evicted past the limit |
IMBI_AUTH_API_KEY_MAX_LIFETIME_DAYS |
365 |
Maximum lifetime allowed when creating an API key |
MFA (TOTP)¶
| Variable | Default | Description |
|---|---|---|
IMBI_AUTH_MFA_ISSUER_NAME |
Imbi |
Issuer name shown in authenticator apps |
IMBI_AUTH_MFA_TOTP_PERIOD |
30 |
TOTP time step in seconds |
IMBI_AUTH_MFA_TOTP_DIGITS |
6 |
TOTP code length |
Rate Limiting¶
Values use the slowapi <n>/<period> format (e.g. 5/minute, 100/hour).
| Variable | Default | Description |
|---|---|---|
IMBI_AUTH_RATE_LIMIT_LOGIN |
5/minute |
Password login attempts |
IMBI_AUTH_RATE_LIMIT_TOKEN_REFRESH |
10/minute |
Refresh-token exchanges |
IMBI_AUTH_RATE_LIMIT_OAUTH_INIT |
3/minute |
OAuth authorization-flow initiations |
IMBI_AUTH_RATE_LIMIT_API_KEY |
100/minute |
API-key-authenticated requests |
OAuth Behavior¶
OAuth provider credentials (Google, GitHub, generic OIDC client IDs and secrets) are no longer environment variables — they are managed at runtime through the admin API and stored encrypted in the graph using IMBI_AUTH_ENCRYPTION_KEY. The settings below control behavior across all configured providers.
| Variable | Default | Description |
|---|---|---|
IMBI_AUTH_OAUTH_AUTO_LINK_BY_EMAIL |
true |
Auto-link an incoming OAuth identity to an existing local user when emails match. Safe for verified-email IdPs (Google, most OIDC). Disable when you require an admin to manually approve linking. |
IMBI_AUTH_OAUTH_AUTO_CREATE_USERS |
true |
Auto-create a user record on first successful OAuth login. Disable to require pre-provisioned accounts. |
OAuth2 Authorization Server (MCP login)¶
Imbi acts as an OAuth 2.0 Authorization Server so MCP clients (and other
OAuth clients) can log a user in via the browser and obtain an Imbi access
token, instead of pasting a static API key. The flow is authorization-code
with PKCE (S256 required); clients self-register via :rfc:7591 Dynamic
Client Registration. Discovery is published at
/.well-known/oauth-authorization-server (served at the host root); the
endpoints are /auth/authorize, /auth/token, and /auth/register under
the API prefix. /authorize reuses the existing Imbi login (including any
configured upstream IdP), so MCP login inherits MFA and provider rules.
| Variable | Default | Description |
|---|---|---|
IMBI_AUTH_DCR_ENABLED |
true |
Allow OAuth clients to self-register at /auth/register (RFC 7591). Disable to require clients to be provisioned out of band. |
Multi-host deployments (separate public login host)¶
A deployment may be reachable on more than one host — e.g. an internal host that serves the SPA and a separate internet-facing host that fronts the MCP OAuth login for a remote client (Claude Desktop and other remote connectors reach the server from the vendor's cloud, not the user's machine, so the MCP and OAuth endpoints must be publicly reachable).
The issuer and endpoints in the discovery document, the post-login
return_to/login redirect, and the upstream-IdP callback URL are all
derived per request from the host the caller actually reached, rather
than from the single IMBI_API_URL. To prevent a spoofed Host header
from redirecting freshly minted tokens off-origin, a request host is only
honored when it is a trusted origin: the origin of IMBI_API_URL plus
every entry in IMBI_API_CORS_ALLOWED_ORIGINS. Untrusted hosts fall back
to IMBI_API_URL. This also requires IMBI_API_FORWARDED_ALLOW_IPS to be
set so the request scheme/host are trusted from the proxy.
To enable a separate public login host:
- Add the public origin to
IMBI_API_CORS_ALLOWED_ORIGINS(e.g.https://imbi-public.example.com).IMBI_API_URLstays pointed at the internal host, so internal SPA traffic is unaffected. - Route
/mcp,/.well-known/oauth-*, and/api/auth/*to the service on that public host. If the public host should support browser-based login (not just MCP), also serve the SPA's login UI assets — the SPA entry document (index.html) served at/and/login, plus its static assets (*.js,*.css, images) — so the login page renders on that host. - If login uses an upstream IdP (Google/GitHub/OIDC), register the public
host's callback —
https://<public-host>/api/auth/oauth/<slug>/callback— with that provider in addition to the internal one, since the callback now names whichever host the user logged in from.
Known limitation
Standard OAuth provider logins (Google/GitHub/OIDC) and MCP OAuth
client flows are fully per-host aware. The identity-plugin connect
flow (linking an additional identity from the account settings UI) is
not yet derived per host and continues to use IMBI_API_URL, so its
callback must remain registered against the IMBI_API_URL origin.
Other Settings¶
Releases (IMBI_RELEASES_*)¶
The [releases] section in config.toml maps to these.
| Variable | Default | Description |
|---|---|---|
IMBI_RELEASES_VERSION_FORMAT |
semver |
Version-string format enforced on Release.version. Other values supported by imbi_common.versioning.VersionFormat. |
Embeddings (EMBEDDINGS_*)¶
Controls embedding generation for vector search. The [embeddings] section in config.toml maps to these.
| Variable | Default | Description |
|---|---|---|
EMBEDDINGS_ENABLED |
true |
Master enable/disable for embedding generation |
EMBEDDINGS_DEFAULT_MODEL |
text |
Key into EMBEDDINGS_MODELS used by default |
EMBEDDINGS_MODELS |
{text: {fastembed_id: BAAI/bge-small-en-v1.5, dimensions: 384}} |
JSON map of model key → {fastembed_id, dimensions} |
Reload Watch Paths¶
| Variable | Default | Description |
|---|---|---|
IMBI_RELOAD_DIRS |
(none) | OS-pathsep-separated list of directories uvicorn should watch when --dev is set. Useful when running with an editable imbi-common checkout, e.g. IMBI_RELOAD_DIRS=../imbi-common/src. |
Standard OpenTelemetry and uvicorn¶
The Compose stack and just docker populate the standard OTEL_* and UVICORN_* environment variables. They are read by the OpenTelemetry SDK and uvicorn directly — Imbi does not redefine them. The .env written by just docker sets sensible development defaults pointing at the bundled Jaeger container:
| Variable | Set by just docker to |
|---|---|
OTEL_SERVICE_NAME |
imbi-api |
OTEL_TRACES_EXPORTER |
otlp |
OTEL_LOGS_EXPORTER |
none |
OTEL_METRICS_EXPORTER |
none |
OTEL_EXPORTER_OTLP_ENDPOINT |
<host>:<jaeger-otlp-port> |
OTEL_EXPORTER_OTLP_TRACES_INSECURE |
true |
OTEL_RESOURCE_ATTRIBUTES |
service.name=imbi-api,service.environment=development |
In production, point OTEL_EXPORTER_OTLP_ENDPOINT at your collector.
Docker Compose Services¶
The development stack defined in compose.yaml runs the following with ports mapped to ephemeral host ports (resolved by just docker and written into .env):
| Service | Container Port | Purpose |
|---|---|---|
postgres (ghcr.io/aweber-imbi/postgres:latest) |
5432 | PostgreSQL + Apache AGE + pg_cron + pgvector |
clickhouse |
8123 (HTTP), 9000 (native) | Analytics |
valkey |
6379 | Cache |
localstack |
4566 | S3-compatible storage |
mailpit |
1025 (SMTP), 8025 (web UI) | SMTP capture |
jaeger |
4317 (OTLP), 16686 (UI) | Tracing |
Use docker compose port <service> <container-port> to look up the assigned host port, or read it from the generated .env.
Configuration File Format¶
config.toml Example¶
[server]
environment = "production"
host = "0.0.0.0"
port = 8080
cors_allowed_origins = ["https://imbi.example.com"]
forwarded_allow_ips = "10.0.0.0/8"
url = "https://imbi.example.com/api"
[postgres]
url = "postgresql://imbi:secret@db-prod:5432/imbi"
graph_name = "imbi"
min_pool_size = 5
max_pool_size = 50
[clickhouse]
url = "clickhouse+http://clickhouse-prod:8123/imbi"
[valkey]
url = "valkey://valkey-prod:6379/0"
[auth]
# JWT secret and encryption key should come from environment variables:
# IMBI_AUTH_JWT_SECRET, IMBI_AUTH_ENCRYPTION_KEY
jwt_algorithm = "HS256"
access_token_expire_seconds = 3600
refresh_token_expire_seconds = 2592000
password_min_length = 14
max_concurrent_sessions = 5
oauth_auto_link_by_email = true
oauth_auto_create_users = false
[email]
enabled = true
smtp_host = "smtp.example.com"
smtp_port = 587
smtp_use_tls = true
from_email = "noreply@example.com"
from_name = "Imbi Platform"
[storage]
bucket = "imbi-uploads-prod"
region = "us-east-1"
create_bucket_on_init = false
[releases]
version_format = "semver"
.env Example (development)¶
# Application
ENVIRONMENT=development
IMBI_API_HOST=localhost
IMBI_API_PORT=8000
IMBI_API_URL=http://localhost:8000
IMBI_API_CORS_ALLOWED_ORIGINS='["http://localhost:5173"]'
# Stores
POSTGRES_URL=postgresql://postgres:secret@localhost:5432/imbi
CLICKHOUSE_URL=clickhouse+http://default:password@localhost:8123/imbi
VALKEY_URL=valkey://localhost:6379/0
# Auth secrets (production: pull from a secrets manager)
IMBI_AUTH_JWT_SECRET=...generate-with-secrets.token_urlsafe(32)...
IMBI_AUTH_ENCRYPTION_KEY=...generate-with-Fernet.generate_key()...
# Object storage (LocalStack in dev)
S3_ENDPOINT_URL=http://localhost:4566
S3_ACCESS_KEY=test
S3_SECRET_KEY=test
S3_BUCKET=imbi-uploads
S3_REGION=us-east-1
# Email (Mailpit auto-configures if these are left at defaults)
IMBI_EMAIL_ENABLED=true
IMBI_EMAIL_FROM_EMAIL=noreply@imbi.example
IMBI_EMAIL_FROM_NAME=Imbi Development
# Tracing
OTEL_SERVICE_NAME=imbi-api
OTEL_TRACES_EXPORTER=otlp
OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317
OTEL_EXPORTER_OTLP_TRACES_INSECURE=true
Production Considerations¶
Security¶
- JWT and encryption keys: Always set
IMBI_AUTH_JWT_SECRETandIMBI_AUTH_ENCRYPTION_KEYexplicitly. Auto-generated values change every restart and silently invalidate all tokens and encrypted secrets. - CORS: Limit
IMBI_API_CORS_ALLOWED_ORIGINSto the exact origins that need API access. Credentials and theAuthorizationheader are allowed for these origins. - Reverse proxy: Set
IMBI_API_FORWARDED_ALLOW_IPSto the trusted proxy CIDR(s) — otherwise rate limiting keys on the proxy IP. - TLS: Terminate HTTPS at the proxy.
- Database credentials: Use strong passwords and rotate regularly; pass via env vars only.
- Secrets management: Store sensitive values in AWS Secrets Manager, HashiCorp Vault, or your platform's secret store.
Performance¶
- Postgres pool size: Tune
POSTGRES_MAX_POOL_SIZE(and_MIN_) for your concurrency and DB capacity. - ClickHouse retention: Configure TTL on analytics tables to match your data-retention policy.
- Access token expiry:
IMBI_AUTH_ACCESS_TOKEN_EXPIRE_SECONDS=900(15 min) is a common production choice; refresh-token rotation makes shorter lifetimes practical.
Monitoring¶
- OpenTelemetry: Point
OTEL_EXPORTER_OTLP_ENDPOINTat your production collector (Jaeger, Honeycomb, Datadog, etc.). - Health checks: Use
/statusfor load-balancer health probes.
Advanced¶
Loading Configuration Programmatically¶
from imbi_api import settings
config = settings.load_config()
print(config.server.environment, config.server.host, config.server.port)
print(config.postgres.url)
print(config.auth.jwt_algorithm)
print(config.email.smtp_host)
print(config.storage.bucket)
# Or instantiate a single section directly (env vars and .env apply):
postgres_settings = settings.Postgres()
server_config = settings.ServerConfig()
Environment-Specific Configuration¶
# Development — .env written by `just docker`
just serve --dev
# Staging — point at a staging config file
cp /path/to/staging/config.toml ./config.toml
uv run imbi-api serve
# Production — system config + injected secrets
# Config at /etc/imbi/config.toml
export IMBI_AUTH_JWT_SECRET="$(load-from-secrets-manager)"
export IMBI_AUTH_ENCRYPTION_KEY="$(load-from-secrets-manager)"
export POSTGRES_URL="$(load-from-secrets-manager)"
uv run imbi-api serve
Configuration Best Practices¶
- Secrets via environment variables: Never check in passwords, JWT secrets, or Fernet keys.
config.tomlfor structure: Put non-sensitive settings in version-controlled config files.- Example configs: Commit
config.example.toml, never the realconfig.toml. - Deployment automation: Render configs via Ansible/Terraform/Helm.
Troubleshooting¶
PostgreSQL / AGE Connection Issues¶
Verify the AGE and pgvector extensions are loaded.
ClickHouse Connection Issues¶
HOST_PORT=$(docker compose port clickhouse 8123 | cut -d: -f2)
curl http://localhost:${HOST_PORT}/ping
docker compose logs clickhouse
Authentication Issues¶
# Confirm secrets are present (do not echo the values in shared output)
env | grep -E '^(IMBI_AUTH_JWT_SECRET|IMBI_AUTH_ENCRYPTION_KEY)=' | wc -l
# Should print 2 in production.
just serve --dev # watch for JWT-related warnings on startup
See Also¶
- Architecture Decision Records — Key architectural decisions
- GitHub Repository — Source code and issues