Managing Applications
An application is a deployable service or frontend that belongs to a project. It is defined once and then enabled per tenant. Each application has its own settings layer, ESB endpoints, triggers, deployments and data dependencies.
Applications are project-level
An application belongs to a project, not to a tenant. One row per project serves every tenant it is enabled for, through a Tenant2Application join. See Multi-tenancy.
Creating an application
- Open a project and click Applications in the sidebar (
/<project>/applications). - Click New application.
- In the Create application dialog fill in:
- Name (required, up to 100 characters).
- Slug (required): lowercase letters, digits and single hyphens (
^[a-z0-9]+(?:-[a-z0-9]+)*$), up to 50 characters, unique within the project. The dialog auto-fills upper-case initials from the name; replace them with a lowercase slug or the server rejects the save. - Version: defaults to
0.1.0(up to 50 characters). - Description: free text, may be empty.
- Click Create. You land on the new application's page.
The application is not visible to any tenant yet. Enable it per tenant (see Enabling for tenants).
The applications list
| Column | Meaning |
|---|---|
| # | Slug |
| Name | Display name |
| Version | Version string |
| Created at / Created by | Creation metadata |
| Updated at / Updated by | Last change metadata |
Click #, Name, Version, Created at or Updated at to sort. There is no filter box.
The application page
Click a row to open the application (/<project>/applications/<app>). The page shows:
- A header with a Deployments button.
- The application dashboard (deployments, rendered config, drift, spec history, triggers, enabled tenants).
- A metadata table: ID, Version, Created by, Created at, Updated at.
- Tenant access: a chip Enabled for n of m tenants and a row per tenant with Enable, Disable and Configure. A row's status is shown only for tenants you changed in this session; the count is authoritative.
- Data dependencies: managed data stores declared for the application (
maintainer/ownercan add, edit and delete; deleting asks you to type the slug). - Application members: users with the application roles
readonlyornormal. The banner on the card says it: Application roles control external UI access only. Manager access is controlled by tenant/project roles.
The sidebar section Application links to:
| Sidebar entry | Path |
|---|---|
| Application details | /<project>/applications/<app> |
| Application settings | /<project>/applications/<app>/settings, see Settings Editor |
| Endpoints | /<project>/applications/<app>/endpoints, see Endpoints |
| Triggers & cron | /<project>/applications/<app>/triggers, see Triggers & Automation |
| Deployments | /<project>/applications/<app>/deployments |
Enabling for tenants
An application is offered to a customer by enabling it for that tenant; nothing is cloned.
- UI: on the application page, Tenant access → Enable / Disable per tenant. The same buttons exist on the tenant page under Applications. Configure opens the Per-tenant configuration dialog, which edits the (tenant, application) settings leaf (Frontend and Backend key-value pairs).
- CLI:
pfy application enable --project <p> --app <app> --tenant <t>,pfy application disable ...,pfy application list-tenants --project <p> --app <app>. - API:
setApplicationEnabledForTenant(tenantId, applicationId, enabled),updateTenant2Application(id, input),tenant2Applications(applicationId: ...).
All of these need project maintainer or owner.
The cross-tenant promoteApplication and copyApplication mutations (and pfy application promote / copy) carry @deprecated directives in the schema and always return an error. To move per-tenant configuration between two enablements use pfy application config-copy --from <joinId> --to <joinId>.
Withdrawing an application from a tenant
Disabling is not the same as withdrawing. Enable / Disable flips the enabled flag on the join row; the row stays, keeps that tenant's configuration leaf, and blocks both deleteApplication and deleteTenant. Removing the row is a second, separate step:
mutation {
withdrawApplicationFromTenant(tenant2ApplicationId: "join-uuid")
}It refuses while the pair is still enabled ("disable it first"), and while a dedicated deployment of this application exists for this tenant: those rows are invisible on the tenant surface, so withdrawing would strand a running stack with nothing left to find it from. Move the tenant back to shared mode (setTenantDeploymentMode) first.
The withdrawal deletes the pair's settings leaf with it, and audits the shape of what was destroyed (how many enabled modules, pilet pins and key-value pairs each half held), never the values. It needs project maintainer or owner on the application's project, or the system administrator role.
API only
There is no UI button and no pfy subcommand for the withdrawal. Get the join id from pfy application list-tenants --project <p> --app <app> and call the mutation directly. The two steps are deliberately not folded into one: the disable is the reversible step that changes what a tenant's users see, and the withdrawal is the irreversible one.
Editing an application
Renaming needs the project role maintainer or owner, or the system administrator role. The rename is not cosmetic: the slug is a hostname derivation input, so changing it repoints the live vhost of every running deployment (<app>.<tenant>.<base>) and propagates onto the primary service the Nomad job is built from.
The UI has no edit form. Use the CLI or the API:
pfy application update <app-slug> --project <project-slug> --name "New name" --version 1.2.0 --description "..."mutation {
updateApplication(
id: "app-uuid"
input: { name: "New name", version: "1.2.0", description: "..." }
) {
id
name
slug
version
}
}UpdateApplicationInput accepts name, slug, description and version.
Deleting an application
Deleting needs the same authority.
The delete is refused while children exist
Deleting is not offered in the UI. pfy application delete <app-slug> --project <project-slug> and deleteApplication(id) are refused while the application still has endpoints, triggers, registered backends, deployments, data dependencies or tenant offers; the error names each blocker with a count. Remove those first.
The tenant offers are the one blocker whose remedy is not "delete them first", and the message says so: "withdraw the application from each tenant first: setApplicationEnabledForTenant(enabled: false), then withdrawApplicationFromTenant". A Tenant2Application row blocks whether or not it is enabled, and disabling a pair that has no row creates one. See Withdrawing an application from a tenant.
The delete writes an audit row before it removes anything, recording the application's name, slug, service count and membership count. See Audit Logs.
Domains
Hostnames are not configured on the application itself. They come from the application's Deployments: each deployment carries explicit domains, and an environment with a Base domain derives <app>.<base> for shared tenants and <app>.<tenant>.<base> for dedicated ones. DNS for those names must point at the proxy. See Multi-tenancy.
How the pieces fit
Project: MyProduct
├─ Environments: dev, qas, run (where it is deployed)
├─ Applications: portal, api (defined once)
└─ Tenants: acme, globex (customer organizations)
├─ acme ← portal enabled, api enabled
└─ globex ← portal enabled- Microservices: one application per service in the same project.
- White-label: one application, one tenant per customer; branding lives in the (tenant, application) settings leaf and tenant settings, not in a copy of the application.
Troubleshooting
A tenant does not see the application
It is not enabled for that tenant. Check Tenant access on the application page.
Endpoints are not reachable
Check that the endpoint is Enabled, that the calling machine user has an enabled access row on the endpoint, and that the caller uses /esb/<applicationId><consumeURL>. See Endpoints.
Settings do not take effect
The frontend cascade takes only module fields from the application layer; tenant settings and the (tenant, application) leaf override it. See Settings Editor.
Triggers do not fire
Check that the trigger is enabled and that a backend is registered and healthy for the application. See Triggers & Automation.