From Solo Dev to Team: How Filess Models Multi-Tenancy with Organizations, Namespaces, and RBAC
You start as a solo developer. One account, one database, no access control needed.
Then you hire a backend developer. You want them to see the production database metrics but not be able to delete anything. Then a contractor joins for three months — you want them to access the staging namespace only. Then your company gets acquired and the acquirer wants their DevOps team to manage infrastructure without touching billing.
Most database platforms weren't designed for this. You end up sharing root credentials via Slack DMs and hoping nobody does something irreversible.
Filess was designed from day one for teams that grow. Here's how the access model works.
The Three-Layer Model
Access in Filess is organized in three layers:
Organization
└── Namespace (prod / staging / ci / team-a / team-b)
└── Resources (databases, tunnels, hosting projects)
Organizations
An Organization is the top-level billing and access unit. It represents a company, a team, or a project. All resources — databases, tunnels, hosting deployments — belong to an organization.
Key organization-level permissions:
organization:view— Can see the organization exists.organization:update— Can edit organization settings.organization:billing:view— Can see billing and invoices.organization:invite:create— Can invite new members.organization:transfer_ownership— Can change who owns the organization.
Namespaces
A Namespace is a logical partition within an organization. You might have:
prod— Production databases, locked down. Only senior engineers can touch.staging— Staging databases. Full team access.ci— Ephemeral databases created and destroyed by CI pipelines.team-frontend— Backend databases that the frontend team needs read access to.
Namespaces let you implement environment isolation without separate accounts. The same team member can have write access in staging and read-only access in prod — from a single login.
Namespace-level permissions:
namespace:create_in_organization— Can create new namespaces.namespace:view_in_organization— Can see a namespace.namespace:update_in_organization— Can rename/modify a namespace.namespace:delete_in_organization— Can delete a namespace and all its resources.
Resources
Resources (databases, tunnels, SSH keys, IP allowlists, hosting projects) live inside namespaces. Each resource type has its own granular permission set.
Roles and Groups
Rather than assigning permissions directly to users (which doesn't scale), Filess uses Roles and Groups.
Roles
A Role is a named set of permissions. You create roles that match your team structure:
Example: db-reader
database:view_in_namespace
metrics:view_in_database
log:view_in_database
backup:view_in_database
Example: db-operator
database:view_in_namespace
database:update_in_namespace
metrics:view_in_database
log:view_in_database
backup:create_in_database
backup:view_in_database
backup:restore_in_database
maintenance_window:view_in_database
maintenance_window:create_in_database
Example: db-admin
database:create_in_namespace
database:view_in_namespace
database:update_in_namespace
database:delete_in_namespace
... (all database permissions)
backup:*
maintenance_window:*
alert_rule:*
Roles are defined at the organization level and can be applied to any namespace within that organization.
Groups
A Group is a collection of users. Instead of assigning roles to individual users, you assign roles to groups, then add users to groups.
Why groups?
- When an employee joins, add them to the right group and they inherit all permissions immediately.
- When an employee leaves, remove them from the group and all access is revoked in one operation.
- When you onboard a contractor, create a time-limited group with restricted access.
Group: "backend-engineers"
Members: alice, bob, charlie
Namespaces: prod (role: db-operator), staging (role: db-admin)
Group: "frontend-engineers"
Members: diana, erin
Namespaces: staging (role: db-reader)
Group: "ci-bots"
Members: github-actions-bot
Namespaces: ci (role: db-admin)
The Full Permission Matrix
Here's every permission in the system, organized by resource type:
Databases
| Permission | What it allows |
|---|---|
database:create_in_namespace | Create new databases |
database:view_in_namespace | See database details and connection info |
database:update_in_namespace | Modify database configuration |
database:delete_in_namespace | Delete databases |
metrics:view_in_database | See Prometheus metrics and charts |
log:view_in_database | Access database logs |
Backups
| Permission | What it allows |
|---|---|
backup:create_in_database | Trigger manual backups |
backup:view_in_database | See backup history |
backup:restore_in_database | Restore from backup (including PITR) |
Maintenance Windows
| Permission | What it allows |
|---|---|
maintenance_window:view_in_database | See scheduled maintenance |
maintenance_window:create_in_database | Schedule maintenance windows |
maintenance_window:update_in_database | Modify existing windows |
maintenance_window:delete_in_database | Cancel scheduled maintenance |
maintenance_window:change_scheduled_date | Reschedule maintenance |
Alert Rules
| Permission | What it allows |
|---|---|
alert_rule:view_in_database | See alert configuration |
alert_rule:create_in_database | Create new alert rules |
alert_rule:update_in_database | Modify alert rules |
alert_rule:delete_in_database | Remove alert rules |
Tunnels
| Permission | What it allows |
|---|---|
tunnel:view_in_namespace | See tunnel details |
tunnel:create_in_namespace | Create tunnels |
tunnel:update_in_namespace | Modify tunnel configuration |
tunnel:delete_in_namespace | Delete tunnels |
SSH Keys
| Permission | What it allows |
|---|---|
sshkey:create_in_organization | Add SSH keys |
sshkey:view_in_organization | List SSH keys |
sshkey:update_in_organization | Modify SSH keys |
sshkey:delete_in_organization | Remove SSH keys |
IP Allowlists
| Permission | What it allows |
|---|---|
ip:create_in_namespace | Add IP allowlist entries |
ip:view_in_namespace | See IP allowlists |
ip:update_in_namespace | Modify allowlist entries |
ip:delete_in_namespace | Remove allowlist entries |
Tailscale
| Permission | What it allows |
|---|---|
tailscale:view_in_organization | See Tailscale configurations |
tailscale:create_in_organization | Add Tailscale configs |
tailscale:delete_in_organization | Remove Tailscale configs |
A Real Scenario: Fintech Startup
You're building a fintech product. Here's how you'd structure Filess access:
Organization: acme-fintech
Namespaces:
- prod (Production databases)
- staging (Staging databases)
- dev (Development databases)
- ci (CI ephemeral databases)
Groups:
- sre-team
prod: db-admin
staging: db-admin
dev: db-admin
ci: db-admin
- backend-team
prod: db-reader ← Can see metrics, not modify
staging: db-operator ← Can manage backups, not delete
dev: db-admin ← Full control
ci: db-admin
- auditors
prod: db-reader ← Read-only, can see audit logs
staging: (no access)
dev: (no access)
- github-actions
ci: db-admin ← Create/delete ephemeral DBs
staging: db-operator ← Run migrations on staging
A new backend engineer joins → add to backend-team. They immediately have the right access to every environment.
A compliance audit starts → add the auditor to auditors. They can see production metrics and logs without being able to modify anything.
A contractor leaves → remove from their group. All access revoked instantly, no password rotation needed.
Audit Logs
Every administrative action is logged:
2026-04-01 09:14 UTC [email protected] created database "payments-prod" namespace=prod
2026-04-01 09:45 UTC [email protected] created backup database="payments-prod"
2026-04-02 14:22 UTC [email protected] updated alert rule "High CPU" database="payments-prod"
2026-04-03 03:17 UTC github-actions created database "pr-452-test" namespace=ci
2026-04-03 03:31 UTC github-actions deleted database "pr-452-test" namespace=ci
The audit log is viewable by anyone with audit_log:view_in_organization permission. Export it for compliance purposes.
The Security Model Under the Hood
Permission checks happen server-side on every API request. The middleware:
- Extracts the authenticated user from the session or JWT token.
- Determines the target resource and what action is being performed.
- Looks up the user's group memberships.
- For each group, checks if the group has a role assigned to the relevant namespace.
- Checks if that role includes the required permission.
- If any path returns
true, the request proceeds. Otherwise,403 Forbidden.
// From the codebase — every protected controller wraps this check:
if (!(await doesUserHavePermission(
req.user,
[ActionTypes.DATABASE_DELETE_IN_NAMESPACE],
{ organizationId: org.id, namespaceId: namespace.id }
))) {
throw new FilessError(`You do not have permission to delete this database`, 403, true);
}
There's no way to bypass this check by passing different parameters in the request body — the resource ownership is always verified independently.
Starting Simple
You don't have to use all of this on day one. When you're a solo developer:
- You have one organization.
- You have one namespace.
- You're the owner — you have all permissions.
You can add team members, create namespaces, define roles, and build groups incrementally as your team grows. The system scales with you rather than forcing you to over-engineer from the start.
What's Next
The RBAC system is actively being extended. On the roadmap:
- Time-limited group memberships (perfect for contractors).
- Per-database access grants (in addition to per-namespace).
- API token scoping — limit a token to specific namespaces or operations.
