Anchor provides a robust multi-tenancy system out of the box, allowing you to serve multiple customers (tenants) from a single installation. Each tenant gets their own isolated environment, ensuring data privacy and security.
Tenancy is a core package that requires installation before use.
php dock package:install Tenancy --packagesThis will automatically:
- Copy configuration files to
App/Config/ - Register the
TenancyServiceProvider - Register the
TenantIdentificationMiddleware - Run core migrations (creates
tenanttable in the central database)
Ensure your central database is configured in
App/Config/database.phpbefore running the installation.
The tenancy package defaults to a Separate Database Strategy, where each tenant has their own dedicated database.
- Tenant Identification: Tenants are identified by subdomain (e.g.,
acme.yourdomain.com). - Isolation: Each tenant has a unique database, user, and password.
- Auto-Provisioning: Databases are automatically created and migrated during tenant creation.
- Context Switching: The system automatically switches database connections based on the identified tenant subdomain.
The tenancy configuration is located in App/Config/tenant.php.
return [
'enabled' => true,
'database' => [
'driver' => 'mysql',
'prefix_pattern' => 'tenant_', // e.g., tenant_acme
'default_host' => '127.0.0.1',
'default_port' => '3306',
'migrations_path' => 'App/storage/database/migrations',
],
'cache' => [
'ttl' => 3600,
'key_prefix' => 'tenant:',
],
'excluded_subdomains' => [
'admin', 'api', 'www', 'mail', 'localhost'
],
'excluded_paths' => [
'/health',
'/status',
'/_debug',
],
];The TenantProvisioningService handles the full lifecycle of tenant creation.
use Tenancy\Services\TenantProvisioningService;
$service = new TenantProvisioningService();
$tenant = $service->create([
'name' => 'Acme Corp',
'subdomain' => 'acme',
'email' => 'admin@acme.com',
'plan' => 'pro',
'seed' => true, // Run seeders after migration
]);What happens during provisioning:
- Validation: Checks subdomain format and uniqueness.
- Persistence: Creates a record in the central
tenanttable. - Database Creation: Creates the physical database and a dedicated user.
- Migration: Runs migrations from the configured
migrations_path. - Seeding: Executes the default
TenantSeeder(ifseedis true).
Tenancy identification is handled via middleware. By default, the installer registers TenantIdentificationMiddleware in App/Config/middleware.php:
// App/Config/middleware.php
return [
'web' => [
// ...
Tenancy\Middleware\TenantIdentificationMiddleware::class,
],
'api' => [
// ...
Tenancy\Middleware\TenantIdentificationMiddleware::class,
],
];In Anchor, middleware is applied to groups (like web or api). To exclude specific paths from tenancy identification (e.g., public pages that don't need a tenant context), use the excluded_paths option in App/Config/tenant.php.
In some cases (CLI jobs, background tasks), you may need to switch context manually using the Tenancy facade:
use Tenancy\Tenancy;
use Tenancy\Models\Tenant;
// Switch to a specific tenant
$tenant = Tenant::where('subdomain', 'acme')->first();
Tenancy::setContext($tenant);
// Your code now runs in acme's database...
// Reset to central context
Tenancy::reset();tenant:create Create a new tenant from the CLI:
php dock tenant:create "Acme Corp" acme admin@acme.com protenant:migrate Run migrations for one or all tenants:
# Migrate all tenants
php dock tenant:migrate
# Migrate a specific tenant
php dock tenant:migrate --tenant=acme
# Fresh migration with seeding
php dock tenant:migrate --fresh --seedtenant:list List all registered tenants and their status:
php dock tenant:listTenant database passwords are encrypted at rest using APP_KEY. The Tenant model handles this automatically via attributes:
$tenant->db_password = 'secret-password'; // Encrypted before saving
echo $tenant->db_password; // Decrypted when accessedSubdomains are strictly validated against injection patterns and a list of excluded_subdomains. Only lowercase alphanumeric characters and hyphens are allowed.