Skip to content

tenant ​

pfy tenant lists, creates, reads, updates, and deletes tenants. A tenant is a customer organization inside a project; applications are enabled per tenant. tenant update --deployment-mode also switches a tenant between the shared deployment and its own dedicated one.

Synopsis ​

bash
pfy tenant <subcommand> [flags]

Aliases: tn

Description ​

Tenants provide multi-tenant isolation within a project. Each tenant has separate data, configuration, and applications while sharing the same project infrastructure.

Subcommands ​

  • list - List all tenants in a project
  • create - Create a new tenant
  • get - Get tenant details
  • update - Update a tenant's name, description and/or deployment mode
  • delete - Delete a tenant (prompts for confirmation; requires --yes with --ci)

tenant list ​

List all tenants within a specific project.

Usage ​

bash
pfy tenant list --project <project-slug> [flags]

Flags ​

FlagRequiredDescription
--projectYesProject slug to list tenants from

Example ​

bash
pfy tenant list --project my-saas

Output:

Tenants:
  - Development (dev)
  - Staging (staging)
  - Production (prod)
3 tenants

--limit and --offset page through the list; without --limit every tenant is fetched.

Verbose output:

bash
pfy --verbose tenant list --project my-saas
Connecting to ProductifyFW manager...
Fetching tenants for project 'my-saas'...
Found 3 tenant(s)
Tenants:
  - Development (dev)
    ID: 11111111-2222-3333-4444-555555555555
  - Staging (staging)
    ID: 22222222-3333-4444-5555-666666666666
  - Production (prod)
    ID: 33333333-4444-5555-6666-777777777777
3 tenants

tenant create ​

Create a new tenant within a project.

Usage ​

bash
pfy tenant create \
  --project <slug> \
  --slug <slug> \
  --name <name> \
  [--description <desc>]

Flags ​

FlagRequiredDescription
--projectNo*Project slug (resolved to an ID)
--project-idNo*Project ID (overrides --project)
--slugYesTenant slug (unique within project)
--nameYesTenant display name
--descriptionNoTenant description

* One project selector is needed: --project-id wins, then --project, then the project_id in project.yaml.

Examples ​

Basic creation:

bash
pfy tenant create \
  --project my-saas \
  --slug dev \
  --name "Development"

With description:

bash
pfy tenant create \
  --project my-saas \
  --slug prod \
  --name "Production" \
  --description "Live production environment with customer data"

Output:

Tenant created successfully: Production (prod)

Verbose output:

bash
pfy --verbose tenant create \
  --project my-saas \
  --slug prod \
  --name "Production" \
  --description "Live production environment"
Connecting to ProductifyFW manager...
Creating tenant 'prod' in project ID '01234567-89ab-cdef-0123-456789abcdef'...
Tenant created successfully: Production (prod)
  ID: 33333333-4444-5555-6666-777777777777

tenant get ​

Read one tenant, resolved by slug within a project.

Usage ​

bash
pfy tenant get <slug> --project <project-slug>
FlagRequiredDescription
--projectYesProject slug the tenant belongs to
--slugNoTenant slug (alternative to the positional)

A UUID is refused: the Manager exposes no tenant-by-ID query, so pass the slug (or use pfy tenant list).

bash
pfy tenant get prod --project my-saas
Tenant: Production (prod)
ID: 33333333-4444-5555-6666-777777777777

tenant update ​

Update a tenant's name, description and/or deployment mode.

Usage ​

bash
pfy tenant update <slug|id> --project <project-slug> [flags]

Flags ​

FlagRequiredDescription
--projectNo*Project slug, needed to resolve a tenant slug to its ID
--nameNoNew tenant display name
--descriptionNoNew tenant description
--deployment-modeNoshared or dedicated, the tenant's deployment topology
--yesNoAliases -y, --confirm (legacy). Skips the confirmation gate that --deployment-mode shared triggers; required under --ci or without a TTY

* Not needed when the tenant is addressed by ID.

At least one of --name, --description or --deployment-mode must be given.

Deployment mode ​

--deployment-mode switches a tenant between the shared topology (one backend per application serves every tenant; the tenant is resolved per request from the caller's signed cookie) and the dedicated topology (the tenant gets its own deployment, upstream app--tenant, and hostname app.tenant.<base>, on which the tenant is pinned by the rendered proxy config).

See Shared vs. dedicated tenants for when to choose which, and for the DNS requirements, including the one-label wildcard caveat, which is the single most common way a dedication is left half finished.

Dedicating:

bash
pfy tenant update acme --project retail --deployment-mode dedicated
Tenant acme deployment mode set to dedicated
DNS checklist (2 records):
  - operations.acme.apps.example.com  CNAME proxy.example.com
  - analytics.acme.apps.example.com   CNAME proxy.example.com
The tenant's hostnames will not resolve until these records exist.

The DNS checklist is the actionable half of the operation: nothing provisions DNS for you, and the tenant's hostnames stay dark until the records exist. A *.apps.example.com wildcard does not cover operations.acme.apps.example.com: a wildcard matches exactly one label, and a dedicated hostname has one more.

Un-dedicating (deletes the tenant's dedicated deployments, so it takes the same confirmation gate as tenant delete: on a TTY you type the tenant's slug back; --yes skips the prompt and is mandatory under --ci/non-TTY):

bash
pfy tenant update acme --project retail --deployment-mode shared --yes
Tenant acme deployment mode set to shared
No DNS changes required.

Without --yes on a non-TTY (or under --ci) the CLI refuses before touching the network:

refusing to delete the dedicated deployments of tenant "acme" without confirmation: re-run with --yes (required under --ci or without a TTY)

--confirm is accepted as a legacy alias of --yes.

Data migration is out of scope

Switching modes moves routing, not data. Dedicating seeds deployment specs; un-dedicating deletes them. Neither reads or writes your application's data; moving it between a dedicated backend and the shared one is an application concern. Decide it before flipping the mode.

Combined with other fields, one invocation applies both:

bash
pfy tenant update acme --project retail \
  --name "Acme Corporation" \
  --deployment-mode dedicated

Authorization ​

Changing deployment mode requires system administrator, or the maintainer or owner role on the owning project. Bare project membership is not enough: the project roles are readonly, tester, developer, maintainer and owner, and un-dedicating permanently deletes every dedicated deployment spec. That is a destructive action, so it sits at the same bar as the other deployment-spec mutations (pfy deployment set / apply) rather than below them.

A delegated tenant administrator cannot dedicate their own tenant even holding tenant-lifecycle permission: deployment topology is an infrastructure and cost decision with DNS consequences.

tenant delete ​

Delete a tenant.

Usage ​

bash
pfy tenant delete <slug|id> [--project <project-slug>] [--yes]
FlagAliasesDescription
--projectProject slug, needed to resolve a tenant slug; not needed for a UUID
--idTarget by ID explicitly
--yes-ySkip the confirmation prompt. Required under --ci or when stdin is not a terminal

On a terminal you type the slug (or ID) back to confirm. Output: Deleted tenant <slug>.

Targeting a dedicated deployment ​

Once a tenant is dedicated, an application has more than one deployment per environment: the shared row, plus one per dedicated tenant. Every spec-consuming command therefore accepts --tenant to say which one:

bash
pfy deployment get      --env prod                  # the SHARED deployment
pfy deployment get      --env prod --tenant acme    # acme's DEDICATED deployment
pfy deployment set      --env prod --tenant acme --field resources.memory_mb=8192
pfy deployment snapshot --env prod --tenant acme    # -> deploy/spec.prod.acme.yaml
pfy deployment apply    deploy/spec.prod.acme.yaml --env prod --tenant acme
pfy nomad render        --env prod --tenant acme --tag v1.2.3
FlagMeaning
(omitted)the shared deployment
--tenant <slug>that tenant's dedicated deployment
--deployment-id <id>a specific row; combined with --tenant the CLI verifies they agree instead of silently picking

Two behaviours worth knowing:

  • An --env alias in project.yaml caches the shared deployment it was created against, so --tenant deliberately bypasses that cache; otherwise it would be silently ignored.
  • The upstream (operations--acme) is server-derived. The CLI never computes it, so the job, group, service and task names inherit it with no second implementation to drift.

Without --tenant, a command that resolves by --env/--app alone against a dedicated application reports the ambiguity rather than guessing.

GraphQL Operations ​

GetTenants ​

graphql
query GetTenants(
  $projectSlug: String!
  $filters: [FilterInput!]
  $order: [OrderInput!]
  $pagination: PaginationInput
) {
  tenants(
    projectSlug: $projectSlug
    filters: $filters
    order: $order
    pagination: $pagination
  ) {
    id
    slug
    name
  }
}

CreateTenant ​

graphql
mutation CreateTenant($projectId: ID!, $input: CreateTenantInput!) {
  createTenant(projectId: $projectId, input: $input) {
    id
    slug
    name
  }
}

Common Workflows ​

Create environment-based tenants ​

bash
# Get project ID
PROJECT_ID=$(pfy project get --slug my-project | grep "ID:" | awk '{print $2}')

# Create dev tenant
pfy tenant create \
  --project-id $PROJECT_ID \
  --slug dev \
  --name "Development" \
  --description "Development and testing"

# Create staging tenant
pfy tenant create \
  --project-id $PROJECT_ID \
  --slug staging \
  --name "Staging" \
  --description "Pre-production validation"

# Create prod tenant
pfy tenant create \
  --project-id $PROJECT_ID \
  --slug prod \
  --name "Production" \
  --description "Live customer environment"

Create customer-based tenants ​

bash
PROJECT_ID="01234567-89ab-cdef-0123-456789abcdef"

# Create tenant for customer A
pfy tenant create \
  --project-id $PROJECT_ID \
  --slug customer-a \
  --name "Customer A Corp" \
  --description "SaaS instance for Customer A"

# Create tenant for customer B
pfy tenant create \
  --project-id $PROJECT_ID \
  --slug customer-b \
  --name "Customer B Inc" \
  --description "SaaS instance for Customer B"

Create an application and enable it for a tenant ​

Applications live at the project level; a tenant gets access by enablement:

bash
# Create the application once, at the project level
pfy application create \
  --project my-project \
  --slug webapp \
  --name "Web Application" \
  --version "1.0.0"

# List tenants to find the right one
pfy tenant list --project my-project

# Enable the application for that tenant
pfy application enable --app webapp --tenant prod --project my-project

Best Practices ​

  1. Use dev, staging, prod slugs for environment-based tenants
  2. Use customer identifiers for SaaS isolation
  3. Use lowercase, hyphen-separated slugs
  4. Include the purpose in descriptions
  5. Design the tenant structure before creating it

Multi-tenant Strategies ​

Environment Isolation ​

bash
# Separate environments within same project
pfy tenant create --project-id $ID --slug dev --name "Development"
pfy tenant create --project-id $ID --slug qa --name "QA"
pfy tenant create --project-id $ID --slug prod --name "Production"

Customer Isolation ​

bash
# Separate customers in SaaS application
pfy tenant create --project-id $ID --slug acme-corp --name "Acme Corp"
pfy tenant create --project-id $ID --slug tech-startup --name "Tech Startup Inc"

Geographic Isolation ​

bash
# Region-based tenants
pfy tenant create --project-id $ID --slug us-east --name "US East"
pfy tenant create --project-id $ID --slug eu-west --name "EU West"
pfy tenant create --project-id $ID --slug asia-pac --name "Asia Pacific"

Error Handling ​

Missing required values are caught before any request is made:

Error: provide a slug and name: pfy tenant create --project <slug> --slug acme --name "Acme Corp"
Error: no project: pass --project <slug> or --project-id, or set project_id in project.yaml
Error: nothing to update: pass --name, --description and/or --deployment-mode

Errors from the Manager are printed after failed to create tenant: (or failed to update tenant "<slug>":, failed to delete tenant "<slug>":); the rest of the line is the Manager's message. Typical causes: the slug is already used in the project, the project does not exist, or the slug has characters other than lowercase letters, digits, and hyphens.

See Also ​