Adding a Service¶
Adding a new service to AKKO involves touching several files to ensure the service is routed, secured, monitored, and visible in the cockpit portal. This page provides the full checklist and a concrete example.
Checklist¶
| Step | File | Purpose |
|---|---|---|
| 1 | helm/akko/charts/<sub-chart>/ |
Helm sub-chart (templates, values, helpers) |
| 2 | helm/examples/realm-akko-k3d.json |
OAuth2 client (if the service needs SSO) |
| 3 | scripts/generate-secrets.sh |
Add any new secrets to .env generation |
| 4 | branding/cockpit-react/src/platform/catalog.ts |
Register the service as a capability in a layer |
| 5 | branding/cockpit-react/src/features/<x>/ |
Add a feature page (only if the service is rendered inside the cockpit) |
| 6 | branding/cockpit-react/src/app/App.tsx |
Add the route (only for an integrated feature page) |
| 7 | scripts/start.sh |
Echo the service URL at startup |
| 8 | Documentation | Update architecture docs, README, etc. |
Each step is detailed below, followed by a complete worked example.
Step 1 -- Helm Sub-Chart¶
Create a Helm sub-chart in helm/akko/charts/<service-name>/ and add the dependency to helm/akko/Chart.yaml. Every AKKO service follows a consistent pattern:
- Pinned image tag (never
latest) - Container name prefixed with
akko- - Traefik IngressRoute for HTTPS routing
- Healthcheck with liveness and readiness probes
- Resource limits appropriate for the service
- Security context (
runAsNonRoot,drop: [ALL])
Traefik Labels Pattern¶
All services exposed through Traefik use these four labels:
labels:
traefik.enable: "true"
traefik.http.routers.<name>.rule: "Host(`<subdomain>.{{ .Values.global.domain }}`)"
traefik.http.routers.<name>.entrypoints: "websecure"
traefik.http.routers.<name>.tls: "true"
If the service listens on a non-standard port (anything other than 80), add:
To protect the service behind Keycloak SSO via oauth2-proxy, add the middleware:
traefik.http.routers.<name>.middlewares: "oauth2-auth-chain@file"
# Plus the oauth2 callback router:
traefik.http.routers.<name>-oauth2.rule: "Host(`<subdomain>.{{ .Values.global.domain }}`) && PathPrefix(`/oauth2/`)"
traefik.http.routers.<name>-oauth2.entrypoints: "websecure"
traefik.http.routers.<name>-oauth2.tls: "true"
traefik.http.routers.<name>-oauth2.service: "oauth2-proxy"
Healthcheck Pattern¶
livenessProbe:
httpGet:
path: /<health-path>
port: <port>
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /<health-path>
port: <port>
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
Healthcheck tips
- Use
httpGetprobes for HTTP health endpoints. - Use
execprobes with the service's native healthcheck command when available (e.g.,["traefik", "healthcheck"]or["mc", "ready", "local"]). - Some minimal images (like Ollama's) lack
curlandwget. UsetcpSocketprobes as a fallback.
Step 2 -- Keycloak OAuth Client¶
If the service supports OpenID Connect, add a client to helm/examples/realm-akko-k3d.json
in the clients array. The client ID should match the service name. Set
publicClient: false for server-side (confidential) flows.
Step 3 -- Secret Generation¶
If the service needs credentials, add them to scripts/generate-secrets.sh inside
the heredoc that writes .env:
After editing, delete .env and re-run to pick up the new variables:
Step 4 -- Cockpit Capability¶
The cockpit is a React SPA (branding/cockpit-react/). Register the service as
a capability inside its layer in src/platform/catalog.ts. Each capability
carries the roles allowed to see it and a kind:
kind: 'external'-- the service has its own dedicated UI, opened in a new tab (most services: Trino, Superset, JupyterHub, ...)kind: 'integrated'-- the service is rendered as a page inside the cockpit (a React feature), reached via an internalto: '/x/<slug>'route
{ id: 'governance', fr: 'Gouvernance', en: 'Governance', icon: 'shield', color: '--cl-gov', caps: [
// external tool: opens its own UI in a new tab
{ id: 'myservice', tool: 'myservice', fr: 'Mon service', en: 'My service',
dfr: 'Description courte', den: 'Short description',
vendor: 'myservice', icon: 'database', roles: ENG, kind: 'external', home: true },
] },
The roles field is the visibility gate: canSeeCap(cap, role, toolAccess) in
src/session/SessionProvider reads the same tool-access matrix that drives
the OPA gate (ADR-075), so a card only appears for a user entitled to the tool.
There is no health-proxy to configure: per-tile health comes from the cockpit
backend.
Step 5 -- Feature Page (integrated services only)¶
Skip this step for an external service. If the service is rendered inside
the cockpit, add a feature under src/features/<x>/:
src/features/myservice/
├── MyServicePage.tsx # the page component
└── i18n.ts # FR/EN dictionary (never hard-code strings in JSX)
The page reads its strings from the i18n.ts dictionary via
useLang() / a t() helper, matching the existing features (RAG, NORA, ...).
Step 6 -- Route (integrated services only)¶
Register the page's route in src/app/App.tsx, before the x/:slug catch-all:
The cockpit uses a HashRouter (ADR-074), so no nginx rewrite rule is required to serve the static island.
Step 7 -- Startup URL¶
Add the service URL to the startup banner in scripts/start.sh:
Step 8 -- Documentation¶
Update architecture documentation and any relevant guides to reflect the new service.
Worked Example: Adding "DataHub"¶
Here is a complete, concrete example of adding a hypothetical DataHub metadata catalog service to AKKO.
1. Helm Sub-Chart¶
Create helm/akko/charts/akko-datahub/ with the standard sub-chart structure:
helm/akko/charts/akko-datahub/
├── Chart.yaml
├── values.yaml
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ └── _helpers.tpl
Add the dependency in helm/akko/Chart.yaml:
Example values.yaml:
enabled: true
image:
repository: acryldata/datahub-gms
tag: "v0.13.0"
pullPolicy: IfNotPresent
resources:
requests:
memory: 512Mi
cpu: 250m
limits:
memory: 1Gi
cpu: 500m
2. helm/examples/realm-akko-k3d.json¶
Add a confidential client in the clients array:
{
"clientId": "datahub",
"enabled": true,
"publicClient": false,
"secret": "${KC_CLIENT_SECRET_DATAHUB}",
"redirectUris": ["https://datahub.akko.local/*"],
"webOrigins": ["https://datahub.akko.local"],
"protocol": "openid-connect",
"standardFlowEnabled": true,
"directAccessGrantsEnabled": false
}
3. scripts/generate-secrets.sh¶
Add to the heredoc:
4. branding/cockpit-react/src/platform/catalog.ts¶
DataHub is a metadata catalog with its own UI, so it is an external
capability in the Governance layer:
{ id: 'datahub', tool: 'datahub', fr: 'Catalogue de métadonnées',
en: 'Metadata catalog', dfr: 'Découverte & lignage',
den: 'Discovery & lineage', vendor: 'datahub', icon: 'database',
roles: ENG, kind: 'external', home: true },
5 & 6. Feature page and route (not needed here)¶
DataHub opens in its own tab (kind: 'external'), so no React feature page and
no App.tsx route are required. These two steps only apply to an integrated
service rendered inside the cockpit.
7. scripts/start.sh¶
Verification¶
After completing all steps:
# Regenerate secrets
rm .env && ./scripts/generate-secrets.sh
# Deploy with Helm
helm upgrade akko helm/akko/ -n akko -f helm/examples/values-dev.yaml \
--set-file akko-keycloak.realm.data=helm/examples/realm-akko-k3d.json
# Verify DataHub is healthy
kubectl get pods -n akko | grep datahub
kubectl logs -f deploy/akko-datahub -n akko
# Open the cockpit and confirm the DataHub tile appears (for an entitled role)
open https://demo.akko.local
# Open the service directly
open https://datahub.akko.local