Apps / Service Catalog¶
The DPF (Deployment Platform Framework) lets you deploy managed applications from the b'nerd service catalog. Apps are containerized workloads provisioned and operated on your Kubernetes clusters — databases, monitoring stacks, message brokers, and similar services.
The API is stable. Available app kinds depend on your organization's catalog subscription — contact your account manager to confirm which kinds are active on your organization.
Managed apps (Stack-placed)¶
b'nerd's managed-apps framework lets you run enterprise-grade, operator-backed software on your Kubernetes clusters without operating it yourself — installation, upgrades, and ongoing reconciliation are handled by a dedicated operator per app kind. Managed Nextcloud, Managed GitLab, and Managed Keycloak are the kinds built on this framework today; more are on the way. Everything in this section — Stack placement, the five-phase lifecycle, write-only credentials, and the day-2 operations below — is the shared mechanism every managed kind uses, not something specific to any one of them.
Managed apps differ from the generic CRUD flow above in three ways: they're placed onto a Stack, they carry an explicit five-phase lifecycle instead of a bare status object, and (for kinds that need one) they collect an administrator credential once, write-only, at order time. The rest of this section explains the concepts; for a step-by-step walkthrough see Deploying a Managed App.
What a Stack is¶
A Stack is a Kubernetes cluster that b'nerd has registered and confirmed can run a given category of managed workloads. When you create a managed app you choose (or the create flow auto-selects, when there's only one choice) the Stack it should run on. You don't manage the Stack inventory yourself — b'nerd's platform team registers and maintains Stacks; your view is limited to the ones eligible for the app you're creating.
Every Stack has a visibility that determines which organizations may place apps on it:
| Visibility | Meaning |
|---|---|
public | Open to any organization on the platform. |
shared | Enabled for a specific set of organizations. |
private | Dedicated to a single organization. |
Eligibility — which Stacks show up as choices — is decided by the platform: only Stacks that are confirmed to support the app kind you're deploying, and that your organization is authorized to use, are ever offered. If nothing is eligible yet for a kind, the create flow says so directly (with a link to contact support) instead of showing an empty or misleading list — the kind itself can still be "available" in the catalog even while no Stack currently hosts it; b'nerd is notified and will wire one up.
The instance lifecycle¶
Every managed-app instance moves through up to five phases:
| Phase | Meaning |
|---|---|
requested | Accepted by the platform; not yet started. |
provisioning | Actively being installed or reconfigured. |
ready | Up and running. The instance's URL becomes available. |
failed | Something went wrong. The instance page shows the reason. |
deleting | Being torn down after you requested deletion. |
Status is refreshed on a short polling interval, not pushed live. The instance page shows when it was last synced; if that's more than a few minutes ago it tells you the status may be slightly out of date rather than implying it's live.
Configuring an instance (spec)¶
Kind-specific settings — the knobs each managed kind exposes beyond name and Stack — are submitted as a nested spec object in the create request body (and in a PATCH for day-2 changes other than delete):
POST /apps
Authorization: Bearer <token>
Content-Type: application/json
{
"kind": "Apps::Managed::Gitlab::V2",
"name": "my-gitlab",
"project_id": "<uuid>",
"spec": {
"version": "17.1.0",
"edition": "ce"
}
}
Each kind only accepts its own allowlisted spec keys; an unrecognized key is rejected with a 422 validation_failed response. Currently:
| Kind | spec fields |
|---|---|
| Managed Nextcloud | version, phpMemoryLimit, maintenanceWindow |
| Managed GitLab | version, edition (ce or ee) |
| Managed Keycloak | version, root_redirect — see Managed Keycloak |
Deprecated: flat top-level version
A bare top-level version field (not nested under spec) is still accepted on Nextcloud requests for one release, for backward compatibility with integrations built before the spec envelope shipped. It is deprecated and will be removed once the CLI, Terraform provider, and dashboard have all migrated to spec — check the release notes for the exact removal release before relying on it. New integrations, and every GitLab request, should use the nested spec form.
Managed GitLab¶
Managed GitLab runs both Community Edition (CE) and Enterprise Edition (EE), selected via spec.edition. If omitted, edition defaults to ce.
- CE — no credentials are collected at order time; nothing beyond
specis required. - EE — requires an EE license, submitted once as a write-only credential at create time (
credentials.license.license, the license file contents). Same write-only handling as any other credential: it's stored encrypted and never returned by the API or shown again in the dashboard.
The instance's database, cache, and object storage are always fully managed by the platform — there's nothing to configure or bring your own for these; they come up automatically as part of provisioning.
Known limitations at this release
- Outbound email (SMTP) is not yet configurable for Managed GitLab instances.
- Enterprise Edition combined with the managed Elasticsearch integration has not yet been broadly validated in production — talk to your account manager before relying on this combination.
Managed Keycloak¶
Managed Keycloak's customer-facing surface — create flow, the default instance host, and the admin credential's reveal/audit model — is documented on its own page: Managed Keycloak.
Credentials are write-only¶
Managed-app kinds that need an administrator login — Nextcloud today, with more kinds following the same pattern — collect that credential once, in the create flow's Configure step, and treat it as write-only from that point on:
- The value you submit is encrypted and materialized directly into a Kubernetes Secret inside the instance's own namespace — it isn't held anywhere in the create flow after submission.
- There is no "reveal password" affordance for these kinds — no read endpoint or dashboard view ever returns the credential once it's been set. Managed Keycloak is the one exception: its admin credential can be revealed again later by a project admin, with every reveal audit-logged — see Managed Keycloak — Admin credentials for that model specifically.
- If you'd rather not choose your own value, the Configure step can generate a random one for you locally, in your browser. It's shown once, with a copy-to-clipboard control, and is gone the moment you leave the page — save it before you move on.
Day-2 operations¶
Two operations are available from an instance's detail page once it's ready:
- Change version — swap to a newer version of the underlying operator's release. Only upgrades are offered; downgrades aren't supported. Changing version re-enters the
provisioningphase while the operator reconciles. - Delete — a two-step, deliberately friction-heavy action. Instances are created with deletion protection on by default; you turn it off (typed confirmation required) before the delete action itself becomes available (a second typed confirmation). This is covered in full in the Deploying a Managed App guide.
Endpoints¶
| Method | Path | Description |
|---|---|---|
GET | /apps | List apps |
POST | /apps | Create an app |
GET | /apps/{id} | Show an app |
PATCH | /apps/{id} | Update an app |
DELETE | /apps/{id} | Delete an app |
List apps¶
Returns all apps visible to the authenticated account across all projects in the organization.
Create an app¶
POST /apps
Authorization: Bearer <token>
Content-Type: application/json
{
"app": {
"name": "my-postgres",
"kind": "postgres",
"organization_id": "<uuid>",
"project_id": "<uuid>"
}
}
Returns 201 Created. The kind value determines which service from the catalog is deployed. Available kinds depend on your organization's catalog subscription — query GET /apps to see deployed kinds or contact your account manager for the current catalog.
Show an app¶
Key response fields:
| Field | Description |
|---|---|
id | App UUID |
name | Display name |
kind | App type (e.g. postgres, redis) |
project_id | Project the app belongs to |
organization_id | Organization UUID |
status | Current deployment status object (nullable while provisioning) |
stack_id | Underlying stack identifier (nullable) |
parent_app_id | Parent app UUID for child/plugin apps (nullable) |
deleted_at | Soft-delete timestamp; null for active apps |
Update an app¶
PATCH /apps/{id}
Authorization: Bearer <token>
Content-Type: application/json
{ "app": { "name": "renamed-postgres" } }
Delete an app¶
Returns 204 No Content. Deleting an app deprovisions the underlying workload.
Irreversible for stateful services
Deleting a stateful app (e.g. a database) permanently removes its data. Ensure you have taken a backup or snapshot before proceeding.
Webhooks and reconciliation¶
Apps integrate with the platform's webhook system. When a container image is pushed to the b'nerd registry and a webhook target mapping matches the resource_url, the corresponding app is reconciled automatically — pulling the new image tag and rolling out the update.
Admin API (superadmin)¶
Admin-only
The /admin/apps/* endpoints require a platform-admin account. They are not reachable with an organization-scoped token, and are not part of the interactive API Reference — this section documents the ones customers most often ask support about.
Change an app's plan¶
PATCH /admin/apps/{id}/plan
Authorization: Bearer <admin-token>
Content-Type: application/json
{ "plan": "enterprise" }
Changes an operator-backed app's plan and re-enqueues reconciliation, so the new plan's sizing (replicas, PostgreSQL topology, PgBouncer) flows into the running instance the same way POST /admin/apps/{id}/reconcile does. The instance stays available during the change — see Managed Keycloak — Plans for the customer-facing plan table and how to request a change.
Reverting a plan change is the same call with the previous plan key.
Response 200 — admin app object, data.plan set to the new key.
Response 403 — not a platform-admin account.
Response 404 — unknown app id.
Response 422
{
"request_id": "0197abc-...",
"errors": [
{
"code": "invalid_plan",
"message": "Unknown plan 'no-such-plan' for this app kind",
"status": 422,
"field": "plan",
"source": { "service": "apps", "operation": "set_plan" },
"meta": { "retryable": false }
}
]
}
invalid_plan — the plan key doesn't exist, isn't active, or doesn't belong to this app's kind. plan_change_unsupported — the app's kind doesn't support plan changes through this endpoint.
Reconcile an app¶
Re-runs the app's reconcile pass against its operator binding on demand — the same code path a scheduled reconcile or a plan change uses. Useful when an instance needs to pick up a CR or catalog change without waiting for the next scheduled pass.
Response 200 — admin app object.
Response 404 — unknown app id.
Authorization¶
App operations use the apps permission category:
| Operation | Required permission |
|---|---|
| List / show | apps:read |
| Create | apps:create |
| Update | apps:update |
| Delete | apps:delete |
See Roles & RBAC for which roles carry these permissions.
Troubleshooting¶
| Symptom | Likely cause | Fix |
|---|---|---|
status is null after create | App is still provisioning | Poll GET /apps/{id} until status is non-null |
403 on create | Missing apps:create permission | Check your membership permissions |
| App stuck in a failed state | Upstream cluster issue | Contact support with the app id and stack_id |