Understanding how tenants, domains, and policies relate to each other is the key to mastering Housecarl. Once you grasp this three-level hierarchy, everything else falls into place.
Think of Housecarl's authorization model like a filing system:
┌─────────────────────────────────────────────────────────────┐
│ TENANT: Acme Corporation │ ← Organization
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ DOMAIN: Engineering Team │ │ ← Policy Container
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ POLICY: Engineers can read project documentation │ │ │ ← Access Rule
│ │ │ POLICY: Lead engineers can approve deployments │ │ │
│ │ │ POLICY: Deny access to archived projects │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ DOMAIN: Product Team │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ POLICY: Product managers can read all roadmaps │ │ │
│ │ │ POLICY: Product team can edit their own specs │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Let's break down each level.
A tenant is your organization's isolated workspace in Housecarl. Think of it as your own private authorization server.
If you're building a SaaS application, each of your customers would be a tenant:
Each tenant is completely isolated from the others. Acme's policies can't affect Beta's resources, and vice versa.
Let's say you're building a project management SaaS. You have two customers:
Acme Corporation (Tenant ID: 550e8400-e29b-41d4-a716-446655440000)
Beta Industries (Tenant ID: 660e8400-e29b-41d4-a716-446655440001)
These two tenants share the same Housecarl instance, but their data and policies are completely separate. A user from Acme can never access Beta's resources through Housecarl, even if they somehow knew the resource URIs.
When an authorization request comes in, Housecarl extracts the tenant from the resource URI:
Authorization Request:
User: alice@acme.com
Action: read
Resource: hc://domain/550e8400-e29b-41d4-a716-446655440000/projects/website-redesign
↑
The domain UUID tells Housecarl which policies to use
Even if Alice works for both Acme and Beta, when she's accessing Acme resources, only Acme's policies are evaluated. This is secure by architecture - you don't need to write policies to enforce tenant isolation.
When you sign up for Housecarl (or create a tenant via housectl admin create), you automatically get:
This means you can start using Housecarl immediately without manual setup.
A domain is a logical grouping of policies. Think of it as a folder that contains related access rules.
You could put all your policies in one big pile, but that becomes unmanageable quickly. Domains let you organize policies by:
Here's where domains get powerful: they can inherit from other domains. This is called superior domain relationships.
┌─────────────┐
│ global │ ← Base policies everyone inherits
│ (domain) │
└──────┬──────┘
│ inherits
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│engineering│ │ product │ │ finance │
│ (domain) │ │ (domain) │ │ (domain) │
└────┬─────┘ └────┬─────┘ └──────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ frontend │ │ roadmaps │
│ (domain) │ │ (domain) │
└──────────┘ └──────────┘
When evaluating authorization for a resource in the frontend domain, Housecarl collects policies from:
frontend domain (the target domain)engineering domain (frontend's superior)global domain (engineering's superior)All three sets of policies are evaluated together.
Let's say Acme Corporation organizes their domains by team:
global domain - Policies that apply to everyone:
engineering domain (inherits from global) - Engineering team policies:
engineering-platform domain (inherits from engineering) - Platform subteam:
When a platform engineer tries to access something, Housecarl evaluates ALL these policies:
This means platform engineers automatically get:
Using housectl:
# Create a basic domain in your current tenant
housectl domain create engineering
# Create a domain in a specific tenant
housectl domain create product-team acme-corp
# Create a domain with policies from a directory
housectl domain create api-access acme-corp ./api-policies/
Domains are tenant-scoped, so the same domain name can exist in multiple tenants without conflict.
A policy is the actual rule that determines if access is allowed or denied.
Here's a real policy from Acme's engineering domain:
name = "engineering-read-code"
description = "Engineering team members can read all code repositories"
engine = "RegEx"
invert = false
deny = false
[[statements]]
team = "engineering"
action = "(read|clone|pull)"
object = "hc://domain/550e8400-e29b-41d4-a716-446655440000/repositories/.*"
Breaking it down:
.*)The statement says:
team attribute is "engineering"When multiple policies exist in a domain hierarchy, Housecarl evaluates them using these rules:
Important: Non-matching policies are simply ignored. You don't need every allow policy to match - just one matching allow policy (with no matching deny policies) is sufficient to grant access.
Example: Let's say we have these policies in the engineering domain:
Policy A (allow): team=engineering can read repositories
Policy B (deny): contract_type=contractor cannot access proprietary repos
Scenario 1: Alice (engineer, employee) reads a proprietary repo
Scenario 2: Bob (engineer, contractor) reads a proprietary repo
Scenario 3: Charlie (finance team) reads a repository
Let's walk through how all three levels work together with a realistic scenario.
Acme Corporation (tenant) has this structure:
Tenant: Acme Corp
├── Domain: global
│ ├── Policy: all-users-read-public
│ └── Policy: users-manage-own-profile
│
├── Domain: engineering (inherits from global)
│ ├── Policy: engineers-read-all-code
│ ├── Policy: engineers-deploy-staging
│ └── Policy: deny-contractors-proprietary
│
└── Domain: engineering-platform (inherits from engineering)
├── Policy: platform-deploy-production
└── Policy: platform-manage-infrastructure
Alice is a platform engineer (employee, not contractor). She wants to deploy to production:
{
"context": {
"subject": "alice",
"action": "deploy",
"object": "hc://domain/550e8400-e29b-41d4-a716-446655440000/environments/production",
"team": "platform",
"parent_team": "engineering",
"contract_type": "employee"
}
}
Runnable example note: The UUID above is illustrative. Replace it with a real domain UUID before using it with
housectl authz can-i, REST, or gRPC examples against a live server.
Note:
housectl authz can-iexpects the same wrappedcontextformat.
Step 1: Housecarl identifies this is an Acme Corp resource (from context or JWT)
Step 2: The resource is in the "production" environment, which belongs to the engineering-platform domain
Step 3: Collect all policies from domain hierarchy:
engineering-platform: platform-deploy-production, platform-manage-infrastructureengineering (superior): engineers-read-all-code, engineers-deploy-staging, deny-contractors-proprietaryglobal (superior of engineering): all-users-read-public, users-manage-own-profileStep 4: Evaluate each policy:
Step 5: Combine results:
Alice can deploy to production!
Now let's say Bob (contractor on the platform team) tries the same thing:
Step 4: Evaluate each policy:
Step 5: Combine results:
Bob is blocked from deploying to production because the deny policy overrides the allow policy.
When you create a new domain, you can specify superior domains:
# Create domain that inherits from global
housectl domain create customer-success --superior-domains global
# Create domain that inherits from multiple superiors (if supported)
housectl domain create mobile-engineering --superior-domains engineering mobile
Note: Check current Housecarl version for multiple superior support. As of this writing, single superior is standard.
You can change domain hierarchies by updating the superior domain relationships:
# Update a domain's properties (including superior)
housectl domain add-superior frontend-team engineering
Warning: Changing domain hierarchies affects policy evaluation immediately. Test in a dev tenant first.
1. Start with a global domain
2. Create domains by team or function
3. Use hierarchy for specialization
4. Keep it shallow
5. Name domains clearly
engineering-backend, product-mobile, finance-reportingdomain1, temp, old-policiesNo. Domains are tenant-scoped. Acme's "engineering" domain is completely separate from Beta's "engineering" domain.
No. Each policy belongs to exactly one domain. However, domains can inherit policies from superior domains, so a policy in "global" is effectively available to all child domains.
Deleting a domain is permanent and affects all child domains immediately. Child domains will no longer inherit policies from the deleted domain. Use with extreme caution.
No. Housecarl enforces a Directed Acyclic Graph (DAG) structure. Circular inheritance would make policy evaluation ambiguous and is prevented.
There's no hard limit, but for performance reasons, keep domains focused. If you have 100+ policies in one domain, consider breaking it into multiple domains with a shared superior.
It depends on your security requirements:
Most teams use separate tenants for production vs. non-production.
A domain is purely an authorization concept - it groups policies. It doesn't create resource namespaces. Your resources (like hc://domain/<uuid>/documents/...) can reference any domain at evaluation time via the UUID.
To see your tenant's domain structure:
# List all domains in your current tenant
housectl domain list
# Get details on a specific domain (including superiors)
housectl domain get engineering
Output example:
Domain: engineering
ID: 7c9e6679-7425-40de-944b-e07fc1f90ae7
Tenant: acme-corp
Active: true
Superior Domains:
- global (550e8400-e29b-41d4-a716-446655440000)
Policies: 5
Remember this hierarchy:
TENANT (Organization)
└── DOMAIN (Policy Container)
└── POLICY (Access Rule)
When an authorization request comes in:
Now that you understand the core hierarchy:
The complete DDD perspective and additional details are in the Domain Model Reference.
Ready to put this knowledge into practice? Head to the Quick Start to create your first policy!