Appearance
Multi-Tenancy Configuration
Apache Fineract is multi-tenant by design. Each tenant is an isolated environment with its own database, its own users, its own configuration, and its own data. A single Fineract instance serves all tenants simultaneously, routing each request to the correct database based on a header in the HTTP request.
Architecture
Two separate databases are involved.
Tenant store (fineract_tenants) - a small registry database that Fineract reads on startup and on every request. It holds one row per tenant describing where that tenant's database is located, what credentials to use, and what connection pool settings to apply. This is sometimes called the "hub" database.
Tenant databases - one database per tenant (e.g. fineract_default, fineract_acme, fineract_beta). Each contains the full Fineract schema: loans, clients, accounting, events, jobs, and all other domain tables. These databases are completely isolated - querying one tenant's database has no effect on any other.
HTTP request
│
│ Fineract-Platform-TenantId: acme
▼
┌─────────────────────┐
│ fineract_tenants │ ← tenant store (always one)
│ tenants table │
│ row: identifier=acme
│ schema=fineract_acme
│ host=db.example.com
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ fineract_acme │ ← tenant database
│ (full schema) │
└─────────────────────┘Tenant databases do not have to be on the same server as the tenant store. Each tenant row in fineract_tenants can point to a different host, port, and set of credentials - making it possible to spread tenants across multiple database servers.
Tenant Resolution
Every API request must include the Fineract-Platform-TenantId header. Fineract reads this header, looks up the matching row in fineract_tenants.tenants, and opens a connection to that tenant's database for the duration of the request.
bash
curl -H "Fineract-Platform-TenantId: acme" \
-H "Authorization: Basic $(echo -n 'mifos:password' | base64)" \
https://localhost:8443/fineract-provider/api/v1/clientsIf the header is missing or the identifier does not match any row in the tenant store, the request is rejected.
Environment Variables
Tenant Store Connection (HikariCP)
These variables configure the connection from Fineract to the fineract_tenants registry database.
| Variable | Default | Description |
|---|---|---|
FINERACT_HIKARI_JDBC_URL | jdbc:mariadb://localhost:3306/fineract_tenants | JDBC URL for the tenant store |
FINERACT_HIKARI_USERNAME | root | Username for the tenant store |
FINERACT_HIKARI_PASSWORD | mysql | Password for the tenant store |
FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAME | org.mariadb.jdbc.Driver | JDBC driver class |
FINERACT_HIKARI_MINIMUM_IDLE | 3 | Minimum idle connections in the pool |
FINERACT_HIKARI_MAXIMUM_POOL_SIZE | 10 | Maximum connections in the pool |
FINERACT_HIKARI_IDLE_TIMEOUT | 60000 | Milliseconds before an idle connection is closed |
FINERACT_HIKARI_CONNECTION_TIMEOUT | 20000 | Milliseconds to wait for a connection |
FINERACT_HIKARI_AUTO_COMMIT | true | Auto-commit on the tenant store connection |
FINERACT_HIKARI_TRANSACTION_ISOLATION | TRANSACTION_REPEATABLE_READ | Transaction isolation level |
Default Tenant Provisioning
At startup, Liquibase runs against the tenant store and seeds a default tenant row if one does not already exist. These variables control what that default tenant looks like.
| Variable | Default | Description |
|---|---|---|
FINERACT_DEFAULT_TENANTDB_IDENTIFIER | default | The tenant identifier used in Fineract-Platform-TenantId header |
FINERACT_DEFAULT_TENANTDB_NAME | fineract_default | The database/schema name for the default tenant |
FINERACT_DEFAULT_TENANTDB_DESCRIPTION | Default Demo Tenant | Human-readable label stored in the tenant row |
FINERACT_DEFAULT_TENANTDB_HOSTNAME | localhost | Host of the default tenant's database |
FINERACT_DEFAULT_TENANTDB_PORT | 3306 | Port of the default tenant's database |
FINERACT_DEFAULT_TENANTDB_UID | root | Username for the default tenant database |
FINERACT_DEFAULT_TENANTDB_PWD | mysql | Password for the default tenant database |
FINERACT_DEFAULT_TENANTDB_TIMEZONE | Asia/Kolkata | Default timezone for the tenant (IANA timezone name) |
FINERACT_DEFAULT_TENANTDB_CONN_PARAMS | (empty) | Optional JDBC connection parameters appended to the tenant's connection URL |
FINERACT_DEFAULT_TENANTDB_MASTER_PASSWORD | fineract | Master password used to encrypt the tenant's stored database password |
Read-Only Replica (Optional)
Each tenant can optionally point to a read-only replica for read queries. These variables configure the replica for the default tenant.
| Variable | Default | Description |
|---|---|---|
FINERACT_DEFAULT_TENANTDB_RO_HOSTNAME | (empty) | Read-only replica hostname |
FINERACT_DEFAULT_TENANTDB_RO_PORT | (empty) | Read-only replica port |
FINERACT_DEFAULT_TENANTDB_RO_UID | (empty) | Read-only replica username |
FINERACT_DEFAULT_TENANTDB_RO_PWD | (empty) | Read-only replica password |
FINERACT_DEFAULT_TENANTDB_RO_NAME | (empty) | Read-only replica database name |
FINERACT_DEFAULT_TENANTDB_RO_CONN_PARAMS | (empty) | Additional JDBC connection parameters for the replica |
When no read-only replica is configured, all queries go to the primary.
Per-Tenant Connection Pool
These variables set the HikariCP pool bounds for each tenant's database connection pool. They apply to all tenants unless overridden in the fineract_tenants.tenants row directly.
| Variable | Default | Description |
|---|---|---|
FINERACT_CONFIG_MIN_POOL_SIZE | -1 | Minimum pool size per tenant (-1 uses the HikariCP default) |
FINERACT_CONFIG_MAX_POOL_SIZE | -1 | Maximum pool size per tenant (-1 uses the HikariCP default) |
Database Encryption
Fineract stores each tenant's database password encrypted in the tenant store. The encryption uses AES/CBC/PKCS5Padding by default. The master password used as the encryption key is set via FINERACT_DEFAULT_TENANTDB_MASTER_PASSWORD (for the default tenant) and FINERACT_DEFAULT_MASTER_PASSWORD globally.
When you insert a new tenant row into fineract_tenants.tenants directly, the password field should contain the plaintext password. Fineract will encrypt it on first use via TenantPasswordEncryptionTask. Set the master password consistently across all Fineract instances in a cluster.
Tenant Provisioning
How the Default Tenant Is Created
On first startup, Liquibase runs a migration against fineract_tenants that inserts the default tenant row using the FINERACT_DEFAULT_TENANTDB_* values above. It then runs all Fineract schema migrations against the default tenant's database, creating the full schema.
On every subsequent startup, TenantDatabaseUpgradeService checks each registered tenant and applies any pending Liquibase migrations to bring them up to the current schema version. This happens for all tenants in the registry, not just the default one - so adding a tenant and restarting is enough to provision its schema.
Adding a Tenant Manually (SQL)
Fineract has no built-in REST API for creating tenants. To add a new tenant:
Step 1 - Create the tenant database:
sql
CREATE DATABASE fineract_acme CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON fineract_acme.* TO 'fineract_user'@'%';Step 2 - Insert a row into the tenant store:
sql
INSERT INTO fineract_tenants.tenants (
identifier,
name,
schema_name,
timezone_id,
schema_server,
schema_server_port,
schema_username,
schema_password,
auto_update,
pool_initial_size,
pool_max_active,
pool_min_idle,
pool_max_idle
) VALUES (
'acme',
'Acme MFI',
'fineract_acme',
'Africa/Nairobi',
'db.example.com',
'3306',
'fineract_user',
'plaintext_password', -- encrypted by Fineract on first access
1,
5,
40,
10,
20
);Step 3 - Restart Fineract (or rely on TenantDatabaseUpgradeService if it is configured to run automatically). Fineract detects the new row and runs Liquibase migrations against fineract_acme, provisioning the full schema.
Step 4 - Verify the new tenant:
bash
curl -H "Fineract-Platform-TenantId: acme" \
-H "Authorization: Basic $(echo -n 'mifos:password' | base64)" \
https://localhost:8443/fineract-provider/api/v1/officesThe tenants.auto_update = 1 flag tells Fineract to run migrations for this tenant on startup. Setting it to 0 disables automatic schema upgrades for that tenant.
Finecko Hub
Managing tenants via raw SQL and server restarts is error-prone and slow at scale. Finecko provides a Hub - a web UI and REST API layer that wraps the tenant provisioning process end to end.
With the Finecko Hub you can:
- Create a new tenant in seconds by filling in a form or calling a single API endpoint - no direct database access required
- View all registered tenants, their database locations, timezone settings, and schema versions from a single dashboard
- Enable or disable a tenant without touching the database
- Trigger schema migrations for individual tenants on demand, without restarting the full application
- Monitor connection pool health and active session counts per tenant
- Set tenant-level configuration such as rounding mode and connection parameters through the UI
The Hub exposes a clean REST API so tenant lifecycle management can be embedded in your own provisioning automation or customer onboarding workflows.
For details on the Finecko Hub, contact the Finecko team.
Docker Compose Example (Two Tenants)
yaml
services:
db:
image: mariadb:10.11
environment:
MARIADB_ROOT_PASSWORD: secret
MARIADB_DATABASE: fineract_tenants
volumes:
- db-data:/var/lib/mysql
fineract:
image: apache/fineract:latest
environment:
# Tenant store connection
FINERACT_HIKARI_JDBC_URL: "jdbc:mariadb://db:3306/fineract_tenants"
FINERACT_HIKARI_USERNAME: "root"
FINERACT_HIKARI_PASSWORD: "secret"
# Default tenant
FINERACT_DEFAULT_TENANTDB_HOSTNAME: "db"
FINERACT_DEFAULT_TENANTDB_PORT: "3306"
FINERACT_DEFAULT_TENANTDB_UID: "root"
FINERACT_DEFAULT_TENANTDB_PWD: "secret"
FINERACT_DEFAULT_TENANTDB_IDENTIFIER: "default"
FINERACT_DEFAULT_TENANTDB_NAME: "fineract_default"
FINERACT_DEFAULT_TENANTDB_TIMEZONE: "UTC"
FINERACT_DEFAULT_TENANTDB_MASTER_PASSWORD: "changeme-masterkey"
depends_on:
- db
volumes:
db-data:After startup, insert a second tenant row into fineract_tenants.tenants pointing to a second database (which can be on the same or a different server), then restart Fineract to trigger schema provisioning.
Per-Tenant Timezone
Each tenant row in fineract_tenants.tenants has a timezone_id column. This controls how Fineract interprets dates for that tenant's business logic - repayment schedules, COB runs, report date ranges, and interest calculations all use this timezone.
Use IANA timezone names (e.g. Africa/Nairobi, Asia/Dhaka, America/New_York, UTC). The default is Asia/Kolkata from the seed row, which should be changed for deployments outside India.
Timezone is set when the tenant row is inserted. Changing it after data exists can cause date calculation inconsistencies - coordinate any timezone changes with a maintenance window.
Tenant Isolation
Each tenant's data is fully isolated at the database level. There is no shared application-level state between tenants. A bug or misconfiguration in one tenant's data cannot directly affect another tenant's data.
What is shared across all tenants in a single Fineract instance:
- JVM process and memory
- Thread pools
- Kafka and JMS producer connections (events are tagged with
tenantIdin theMessageV1envelope) - Liquibase schema version history (each tenant's schema versioning is tracked in its own database)
For complete infrastructure isolation (separate JVM per tenant), deploy separate Fineract instances pointed at separate tenant store rows, or use the Finecko Hub's multi-instance management capabilities.
Authentication Per Tenant
Fineract users are stored in each tenant's database (m_appuser table). There is no cross-tenant user - an account in tenant acme cannot authenticate requests to tenant beta.
Each tenant starts with the default admin credentials (mifos / password). These should be changed immediately after provisioning.
Liquibase Migration Threading
Fineract runs Liquibase migrations for all registered tenants at startup. This is deliberately single-threaded (tenant-upgrade-task-executor-core-pool-size=1) to avoid Liquibase locking issues. If you have a large number of tenants, startup time will be proportional to the number of tenants with pending migrations.
The queue capacity for pending migration tasks is controlled by:
bash
FINERACT_TENANT_UPGRADE_TASK_EXECUTOR_QUEUE_CAPACITY=100Increase this if you have more than 100 tenants to migrate at startup.