Application Onboarding Guide

This guide walks through the full journey from zero to a running application on the App Platform — from AppPlatformRegistration to pods serving traffic.

How the App Platform Works

The App Platform uses intent-based provisioning. You declare what you want via Kubernetes custom resources (CRs), and the operator reconciles your intent into running infrastructure. If you delete a CR, the operator cleans up. If something drifts, it reconciles back.

You declare:                          Operator provisions:

  AppPlatformRegistration "my-app"    -> Creates my-app-platform GitHub repo
                                      -> Generates deploy key + registers on GitHub
                                      -> Generates per-platform AGE encryption key
                                      -> Commits Flux config to flux repo
                                      -> Commits .sops.yaml to platform repo
                                      -> Enforces RBAC in environment namespaces

OIDC client registration is handled separately — app teams create labeled Secrets in the <app>-platform namespace and the OidcClientReconciler picks them up. See Enabling SSO below.

Prerequisites

RequirementWho provides it
AppPlatformRegistrationSystem team creates it
Authelia group membershipSystem team configures it
A GitHub repository with K8s manifestsYour team

Part 1: System Team Setup

This section is for system team / platform admins who register new applications.

1.1 Configure Authelia Groups

Create OIDC groups in Authelia for the application team. Convention:

GroupRolePurpose
myapp-opsadminFull CRUD on resources, namespace management
myapp-developerdeveloperRead workloads, exec, port-forward
myapp-viewerreaderRead-only access to workloads and logs

1.2 Create the AppPlatformRegistration

apiVersion: labrats.work/v1alpha1
kind: AppPlatformRegistration
metadata:
  name: my-app
spec:
  displayName: "My Application"
  access:
    - group: myapp-ops
      role: admin
    - group: myapp-developer
      role: developer
    - group: myapp-viewer
      role: reader

What happens when you apply this:

  1. Creates the my-app-platform GitHub repo (private)
  2. Generates an Ed25519 deploy key and registers it on GitHub
  3. Generates a per-platform AGE encryption key for SOPS
  4. Commits Flux config to the Flux repo under apps/my-app-platform/
  5. Commits .sops.yaml to the platform repo root
  6. Discovers namespaces with label labrats.work/app: my-app and creates RBAC

1.3 OIDC clients (optional) {#enabling-sso}

OIDC clients are no longer declared on the APR. To register an Authelia OIDC client for an app, create a labeled Secret in the <app>-platform namespace:

apiVersion: v1
kind: Secret
metadata:
  name: my-app-oidc
  namespace: my-app-platform
  labels:
    labrats.work/oidc-client: my-app           # client_id in Authelia
    labrats.work/app: my-app
type: Opaque
stringData:
  hostname: app.my-app.hcl.labrats.work
  callbackPath: /api/auth/oidc/callback        # optional
  # clientSecret is operator-generated

The OidcClientReconciler watches these Secrets, generates the clientSecret, registers the client with Authelia, and reports status via the labrats.work/oidc-status annotation. Multiple OIDC clients per app are supported — one Secret per client/hostname.


Part 2: Application Team Setup

2.1 Prepare Your Repository

Your repository needs Kubernetes manifests that Flux can apply:

apps.my-app/
├── src/                              # Your application code
├── Dockerfile
└── k8s/
    ├── base/
    │   ├── kustomization.yaml
    │   ├── deployment.yaml
    │   ├── service.yaml
    │   └── ingress.yaml
    └── overlays/
        ├── dev/
        │   └── kustomization.yaml    # namespace: my-app-dev
        └── production/
            └── kustomization.yaml    # namespace: my-app-prod

2.2 Configure the Platform Repo

The operator creates the my-app-platform repo AND scaffolds the standard files into it on first reconcile. You inherit:

my-app-platform/
├── kustomization.yaml                 # root — references every sibling
├── my-app-source.yaml                 # GitRepository → apps.my-app
├── my-app-image-automation.yaml       # ImageRepository + Policy + Update
├── my-app-prod.yaml                   # Flux Kustomization for prod env
├── my-app-platform.yaml               # Flux Kustomization → ./platform
├── .sops.yaml                         # per-app age recipient
└── platform/
    ├── kustomization.yaml
    └── rbac.yaml                      # secret-reader for cross-ns secrets

See the platform-repo layout spec for what each file does.

Clone and customise only if you need to — most apps run with the scaffold unchanged. Common edits:

  • Extra environments. Add my-app-<env>.yaml (dev, staging, admin). See the multi-env image automation cookbook.
  • Extra images. Duplicate the ImageRepository + ImagePolicy blocks in my-app-image-automation.yaml for additional container images.
  • Secrets. Add SOPS-encrypted Secrets under platform/ — they decrypt with your per-app age key, not the cluster key.

2.3 Namespaces

  • Platform namespaces (<app>-platform) are created automatically by the operator
  • App namespaces are created via the dashboard (POST /api/namespaces)
  • All namespaces carry labrats.work/app: my-app
  • Only app-role namespaces appear in the dashboard

2.4 Flux Takes Over

Once the Flux CRs exist, Flux controllers handle the rest:

  1. Source Controller clones your repo via SSH (using the deploy key)
  2. Kustomize Controller runs kustomize build on each overlay path
  3. Applies the resulting manifests to each target namespace
  4. Prunes removed resources (garbage collection)
  5. Repeats every 5-10 minutes

From this point, every git push to your repo automatically deploys to the cluster.

2.5 Verify Your Deployment

# AppPlatformRegistration status
kubectl get appplatformregistration my-app

# Platform repo Flux resources
kubectl get gitrepository -n my-app-platform

# Kustomizations
kubectl get kustomization -n my-app-platform

# RBAC in env namespaces
kubectl get rolebindings -n my-app-dev

# Running pods
kubectl get pods -n my-app-dev
kubectl get pods -n my-app-prod

Part 3: Adding Secrets

Secrets are managed through the dashboard — no need to clone the platform repo or run sops manually.

  1. Navigate to Secrets in the sidebar
  2. Select your app using the context selector
  3. Click Create Secret
  4. Enter a name, select one or more target namespaces, and add key-value pairs
  5. Click Create

The operator automatically encrypts the data using SOPS + AGE, commits it to the platform Git repo, and Flux deploys the decrypted secret to the target namespace(s).

Dashboard → K8s Secret (intent) → Operator (encrypt + commit) → Flux (decrypt + apply)
  • Secrets are write-only — the dashboard shows key names but never values
  • Only admins can create, update, or delete secrets

Part 4: Day-2 Operations

Adding a new environment

Preferred (ADR-006): declare the env on the APR and the operator scaffolds the Flux Kustomization, ImageRepository/Policy, and namespace for you.

spec:
  imageStreams:
    - name: app
      image: ghcr.io/labrats-work/apps.my-app
  environments:
    - name: staging                        # new
      namespace: my-app-staging
      hostname: app.my-app-staging.hcl.labrats.work
      imagePolicy: { kind: strict, range: "=0.75.0" }

Then create the matching kustomize overlay (k8s/overlays/staging/) in your app repo. Image cadence per env is controlled by imagePolicy.kind: loose (auto-rolls), strict (PR-bumped range), or none (no image automation — overlay tag is the source of truth). See the ADR-006 design doc and the multi-env image automation cookbook.

Legacy spec.namespaces[] is still supported (mutually exclusive with spec.environments[]) for apps that hand-author their platform repo files.

Updating access control

Update the spec.access entries in the AppPlatformRegistration:

  • Added groups get new RoleBindings
  • Removed groups lose their RoleBindings
  • Changes reconcile within 5 minutes

Troubleshooting

AppPlatformRegistration not Ready

kubectl get appplatformregistration my-app -o jsonpath='{.status.conditions}'

Common causes:

  • RepoCreationFailed — Check GITHUB_TOKEN permissions
  • DeployKeyFailed — Failed to generate or register deploy key

Can't see my app in the dashboard

Your Authelia groups don't match the spec.access entries. Verify group membership.

Pushed code but nothing deployed

  1. Check GitRepository source: kubectl get gitrepository -n my-app-platform
  2. Check Kustomization status: kubectl get kustomization -n my-app-platform
  3. Check Flux logs: kubectl -n flux-system logs deploy/kustomize-controller

For a wider menu of symptoms, see the disaster-recovery runbook.


Next steps

(force reconcile, suspend, rotate secrets).

image tagging, env vars.

dev/prod cadence + rollback procedure.

canonical file shape the operator scaffolds.