What it is, exactly
Ficus is not a framework and not a library. It's a complete backend system — infrastructure, deployment, multi-tenancy, validation, and async processing — designed and integrated from the start to work as one.
The goal isn't to make systems easier to build. It's to make them hold as the business evolves: new tenants, new markets, new requirements — without accumulating the kind of drift that makes every change feel riskier than the last.
Stack
| Layer | Technology | Role |
|---|---|---|
| Application | Django + DRF + Channels | HTTP API, WebSocket, admin |
| Language | Python 3.13 | — |
| Database | PostgreSQL | One core DB + one isolated DB per tenant |
| Task queue | ficus_channel_tasks | Async processing — Django Channels |
| Cache / channels | Redis | WebSocket layer, caching |
| WSGI server | uWSGI | HTTP requests |
| ASGI server | Uvicorn | WebSocket, async routes |
| Reverse proxy | Nginx | Backend routing |
| Auth | OAuth2 + Google IAP | API authentication, corporate SSO |
| Packaging | Debian (.deb) + bundled wheels | Offline-capable install — dependencies bundled |
Data model
Models are defined declaratively in YAML (entities.yml per module) and auto-generated into Django models at import time. The data model is always derived from one source of truth — no drift between spec and implementation, no manual model synchronization.
# Define the model once — Ficus generates the Django model, migration, and ORM
entities:
- name: Invoice
fields:
- name: tenant
type: FK
to: ficus_multitenant.Tenant
- name: amount
type: DecimalField
- name: issued_at
type: DateTimeField
Architecture
Ficus runs as a set of cooperating processes, each with a defined role. The architecture is designed to be observable, scalable, and restartable without state loss.
Request routing
Nginx routes /async/* to Uvicorn; all other traffic to uWSGI. WebSocket state is held in Redis. Both WSGI and ASGI layers run as systemd services — independent restart, independent scaling.
Multi-tenancy
Each tenant gets a separate PostgreSQL database and a dedicated database user. Isolation is at the database level — not row-level. One tenant's data is structurally unreachable from another tenant's connection: no WHERE clause discipline required, no shared schema risks.
# Tenant routing — resolved per request via Python ContextVar (thread-safe, async-safe) with TenantContext(tenant): Invoice.objects.all() # → queries acme_uk_ficus_db, authenticated as acme_uk_ficus_user # Tenant DB naming is deterministic from the subdomain subdomain: "acme-uk" db_name: "acme_uk_{core_db_name}" db_user: "acme_uk_{core_db_user}" password: # random hash, stored in Tenant model — never in config files
The Django DB router reads the ContextVar on every query. No global state. Works correctly under async, across background workers, and with concurrent requests for different tenants.
Async processing
Background tasks run via ficus_channel_tasks — a lightweight task runner built on Django Channels. Tasks are dispatched to a Redis channel and consumed by a dedicated systemd service (channel-tasks). The task system is tenant-aware by default: each task carries its tenant context, resolved automatically by the worker. No per-tenant queue configuration needed.
For workloads that require powerful multi-process parallelisation, Celery can be integrated as an optional addition. ficus_channel_tasks is the default for production deployments.
Authentication
API access uses OAuth2 (django-oauth-toolkit). For corporate deployments, Google Identity-Aware Proxy (IAP) authentication is available as a drop-in: requests carry a signed IAP assertion that Ficus validates directly — no client-side changes required.
The deployment model
Ficus is distributed as Debian packages with Python dependencies bundled as wheels. Installation is designed to be offline-capable — nearly all dependencies are bundled.
What installation does
/opt/{app_name}. Install Ficus wheels — all dependencies pinned and bundled. No pip calls to PyPI.GET /healthz → 200 OK.Secrets
No .env files in production. Secrets are passed as environment variables to systemd service units — they never touch the filesystem. The Django configuration template is rendered with envsubst at install time and discarded.
DJANGO_SECRET_KEY
FICUS_CORE_DB_PASSWORD
FICUS_PSQL_ROOT_PASSWORD
FICUS_EMAIL_HOST / FICUS_EMAIL_PORT / FICUS_EMAIL_HOST_USER / FICUS_EMAIL_HOST_PASSWORD
IAP_AUDIENCE # if using Google IAP for corporate auth
Migrations
Migrations are managed through ficus-django-admin — the single entry point for all management operations. Core and tenant migrations run separately; tenant DBs only receive migrations for the apps they own.
ficus-django-admin make_ficus_migrations # generate migrations for all modules ficus-django-admin migrate # apply to core DB ficus-django-admin migrate_tenant_db # apply to each tenant DB
Adding a tenant
New tenants are provisioned automatically — the install script handles it. The same commands are also available for admin management at any time. No architecture changes, no downtime.
ficus-django-admin authorize_tenant acme-uk # create DB + user + grant permissions ficus-django-admin migrate_tenant_db # run tenant migrations # Tenant is live.
CI/CD pipeline
Ficus has its own CI/CD system. GitHub Actions triggers on every pull request and pulls the source into the Ficus pipeline, which runs all checks and generates a full report — stored in the database and posted back as PR comments. The pipeline also integrates with GitHub Actions for standard CI workflows.
The flow: pull request → GitHub Actions triggers → source pulled into Ficus CI backend → all checks run → report stored in DB → results posted as PR comments.
# tox environments — what runs on every PR flake # linting typecheck # mypy static analysis 3.13 # full pytest suite on Python 3.13, with coverage docs # documentation generation shell # script safety checks
How integration works
Ficus is a complete system — not a layer you add to existing code. Integration means deploying Ficus for a new service or a new bounded context, and migrating business logic into it incrementally.
What you get on day one
What you bring
Your business logic — modeled in entities.yml, extended in Python. Ficus owns the infrastructure layer; you own the domain layer. The boundary is clear by design and enforced by the system.
Common questions
| Can we run it on AWS or Azure? | Ficus runs on any Linux server. Our current reference infrastructure is GCloud; AWS and Azure deployments are on the roadmap. The Debian packaging model means cloud-specific tooling is minimal. |
| What happens to our existing code? | Ficus is a new system, not a wrapper. The typical approach is to start a new service or migrate one bounded context at a time — not a big-bang rewrite. |
| How long does initial setup take? | From a bare server to a live production system: hours for the infrastructure. Domain modeling is the client's responsibility — Ficus provides the system that runs it. Early conversations typically clarify what the system needs to support. |
| Who manages updates? | Ficus releases versioned Debian distribution packages. Updates follow the same installation flow — no in-place patching, no surprise dependency changes. |
| What does the E2E test suite actually cover? | All REST API endpoints, WebSocket events, multi-tenant DB isolation, migration lifecycle, and background task execution. |
| How does multi-tenancy affect our data model? | Each tenant gets a fully isolated PostgreSQL database. You model the domain once in YAML; Ficus handles the per-tenant provisioning, migrations, and query routing automatically. |