Skip to content

Reference: app.manifest.yml

Every app has an app.manifest.yml at its root. This file is the app's self-description — the portal, gateway, and CI auto-discover apps from these manifests.

The schema is defined in schemas/app-manifest.schema.json (JSON Schema draft 2020-12) and validated in CI. For the rationale behind this design, see ../adr/0005-app-manifest-schema.md.


Full schema (v1)

# REQUIRED — Schema versioning
manifest_version: 1

# REQUIRED — Identity
id:                                   # Stable UUID, generated by script. NEVER changes.
slug: client-invoice-tracker          # kebab-case, unique repo-wide. Used in URLs, paths, DB names.
name: Client Invoice Tracker          # Human-readable display name.
kind: product                         # 'product' | 'client' | 'internal' | 'platform'
description: >                        # One-sentence pitch for the app hub.
  Track invoices for your clients and mark them as paid.

# REQUIRED — Visual identity in the portal
icon: receipt                         # Lucide icon name OR a path like 'assets/icon.svg'
color: '#0ea5e9'                      # Tailwind-friendly hex, used for tile background

# REQUIRED — Networking & routing
routing:
  subdomain: invoices                 # → invoices.localhost in dev, invoices.${SCALR_PUBLIC_DOMAIN} in prod
  ports:
    frontend: 5174                    # Local dev port (allocated by scaffold script)
    backend: 8174                     # Local dev port (allocated by scaffold script)
  health_path: /health                # Backend health endpoint (used by smoke test + gateway)

# REQUIRED — Authentication & entitlements
auth:
  required: true                      # Almost always true. False only for public marketing pages.
  authentik_app_slug: invoice-tracker # Slug of the corresponding Authentik Application.
  required_groups: []                 # Authentik group names. Empty = any authenticated user.
  scopes:                             # OIDC scopes the app requests.
    - openid
    - profile
    - email

# REQUIRED — Data ownership
database:
  name: invoice_tracker_db            # Postgres DB name (generated from slug by script).
  migrations: alembic                 # Migration tool. 'alembic' is the only supported value today.

# OPTIONAL — Visibility in the portal app hub
hub:
  visible: true                       # Show in app hub for entitled users? Default true.
  category: finance                   # Grouping in the hub: 'finance' | 'ops' | 'tools' | 'admin' | etc.
  order: 100                          # Sort order within category. Lower = earlier.

# OPTIONAL — Inter-app dependencies
dependencies:
  apis: []                            # Other SCALR apps this one calls. e.g. ['billing', 'auth']
  events_publish: []                  # Events this app emits. e.g. ['invoice.paid']
  events_subscribe: []                # Events this app reacts to. e.g. ['user.deleted']

# OPTIONAL — Operational metadata
ownership:
  team: platform                      # Owning team / individual.
  contact: nickolai@scalr.com         # Where to route bugs / questions.

lifecycle:
  status: active                      # 'active' | 'beta' | 'deprecated' | 'archived'
  created: 2026-04-29                 # ISO date — set by scaffold script.

# OPTIONAL — Build / deploy hints
build:
  frontend:
    dockerfile: frontend/Dockerfile
    context: frontend
  backend:
    dockerfile: backend/Dockerfile
    context: backend

# OPTIONAL — Resource hints (advisory; for future orchestrator use)
resources:
  frontend:
    memory: 256Mi
    cpu: 100m
  backend:
    memory: 512Mi
    cpu: 250m

Field-by-field reference

Identity

manifest_version (required) — Integer. Currently 1. Lets us evolve the schema without breaking old apps.

id (required) — UUID. Stable, never changes, even if the app is renamed. Entitlements, audit logs, and cross-app references key off this.

slug (required) — kebab-case, unique repo-wide. Used in URLs, paths, DB names. If the app is renamed, the slug and directory change but the id stays.

name (required) — Human-readable display name shown in the portal.

kind (required) — One of product | client | internal | platform. Determines where the app lives in the tree: - productapps/products/<slug>/ - clientclients/<slug>/ - internalapps/admin/ or similar - platformservices/<slug>/

description (required) — One sentence for the app hub tile.

Visual identity

icon (required) — Either a Lucide icon name (receipt, users, chart-line) or a path to an SVG asset (assets/icon.svg).

color (required) — Hex code for the accent color of the app's tile in the hub.

Routing

routing.subdomain (required) — Subdomain prefix. The gateway derives the full domain (<subdomain>.scalr.com).

routing.ports.frontend / routing.ports.backend (required) — Local dev ports. Allocated by the scaffold script. Humans rarely touch these.

routing.health_path (required) — Backend health endpoint. Used by the smoke test and the gateway.

Auth

auth.required (required) — Boolean. Almost always true. false only for public marketing pages.

auth.authentik_app_slug (required) — Slug of the corresponding Authentik Application. The portal uses this to check entitlements.

auth.required_groups (required) — Array of Authentik group names. Empty array means any authenticated user.

auth.scopes (required) — OIDC scopes the app requests. Default [openid, profile, email].

Database

database.name (required) — Postgres DB name. Generated from slug by the scaffold script.

database.migrations (required) — Migration tool. alembic is the only supported value today.

Hub (optional)

hub.visible — Default true. Set to false for apps that exist but shouldn't appear in the hub.

hub.category — Grouping label in the hub.

hub.order — Sort order within category. Lower = earlier.

Dependencies (optional)

dependencies.apis — Other SCALR apps this one calls. Used for impact analysis.

dependencies.events_publish — Events this app emits. Captured even before a message bus exists.

dependencies.events_subscribe — Events this app reacts to.

Ownership (optional)

ownership.team — Owning team or individual.

ownership.contact — Email or handle for bugs/questions.

Lifecycle (optional)

lifecycle.status — One of active | beta | deprecated | archived. The portal uses this to show banners or hide apps.

lifecycle.created — ISO date. Set by scaffold script.

Build (optional)

build.frontend.dockerfile / build.frontend.context — Where the frontend Dockerfile lives. Defaults to frontend/Dockerfile and frontend.

build.backend.dockerfile / build.backend.context — Same for backend.

Resources (optional, advisory)

resources.frontend.memory / resources.frontend.cpu — Currently advisory. When we eventually move from compose to Kubernetes/Nomad, these become real limits.


Validation

infra/scripts/validate-manifest.sh <path> validates a single manifest against the schema. It's invoked by:

  • The scaffold script after generation (sanity check).
  • The smoke test (asserts the manifest is valid).
  • CI on every PR (against every manifest in the repo).

Invalid manifests fail the build. No silent fallbacks.


Auto-discovery

The portal builds the app hub at runtime by:

  1. Scanning apps/**/app.manifest.yml and clients/**/app.manifest.yml.
  2. Filtering by lifecycle.status != "archived" and hub.visible == true.
  3. Querying Authentik for the current user's entitled application slugs.
  4. Intersecting: only apps where auth.authentik_app_slug matches an entitled app are shown.
  5. Grouping by hub.category, sorting by hub.order.

Adding a new app to the hub = creating its manifest. No portal code changes.


Example: minimal manifest after scaffolding

What create-app-from-template.sh produces — fully valid, ready to boot, ready for customisation:

manifest_version: 1
id: 7f3c9a2e-4b1d-4e8a-9c2f-1d3e5b7a9c4f
slug: invoice-tracker
name: Invoice Tracker
kind: product
description: TODO — describe what this app does in one sentence.
icon: square
color: '#64748b'
routing:
  subdomain: invoice-tracker
  ports:
    frontend: 5174
    backend: 8174
  health_path: /health
auth:
  required: true
  authentik_app_slug: invoice-tracker
  required_groups: []
  scopes: [openid, profile, email]
database:
  name: invoice_tracker_db
  migrations: alembic
hub:
  visible: true
  category: tools
  order: 100
ownership:
  team: platform
  contact: nickolai@scalr.com
lifecycle:
  status: active
  created: 2026-04-29