chardma hosting platform - tenant onboarding

Tenant onboarding — the chardma hosting platform

A tenant = one terraform.tfvars entry + one authentik group membership. Everything else is provisioned as code or documented here for the tenant to apply themselves. Their repo is theirs — the platform never writes to it; this page is what we hand them instead.

Provision (platform side)

  1. infrastructure/k3s/terraform/terraform.tfvars — add an entry:

    project_runners = {
      <slug> = {
        project_path = "<Namespace>/<project>"
        tags         = ["<slug>-ci"]
        untagged     = true      # run their Auto DevOps / untagged jobs
        dashboard    = true      # scoped k8s view + Grafana board
      }
    }

    tf-apply provisions: a project-locked runner in the fenced <slug>-ci namespace, ResourceQuota/LimitRange (the costed bound — default 8Gi limits: runner + two concurrent kaniko builds), scoped RBAC, a GitLab environment, user_access kubectl (Operate → Kubernetes, their namespace only) and a per-tenant Grafana folder+board.

  2. authentik — create group tenant:<slug> as a child of vpn-users (terraform: one authentik_group resource beside tenant_msk in authentik-access.tf), then add the tenant’s user to it. The nested chain fastMCP ← vpn-users ← tenant:<slug> means that ONE membership grants VPN auth, MCP access, and their MCP scoping claim. Removing it — or disabling the user — kills all three.

  3. That’s it. No CI variables, no secrets handed over, no clicks beyond the group tick.

What the tenant gets

Capability Where Notes
CI runner their project, tag <slug>-ci + untagged project-locked; jobs are pods in their fenced namespace
Container registry registry.chardma.org.uk/<namespace>/<project> per-project, GitLab-auth’d
Package registry GitLab Packages (npm/PyPI/Maven/generic) on the main host
Static hosting pages.chardma.org.uk/<namespace>/<project>/ GitLab Pages, namespace-in-path
k8s view GitLab → Operate → Kubernetes their namespace only (user_access)
Metrics per-tenant Grafana board their namespace’s usage
VPN L2TP/IPsec to baldwin.chardma.org.uk authentik credentials — see below
MCP (estate tools, scoped) https://fastmcp.chardma.org.uk/mcp over the VPN OAuth via authentik; the tenant:<slug> claim scopes them to their own namespace/project + a read-only toolset

VPN client setup (tenant side)

Built-in L2TP/IPsec client:

Field Value
VPN type L2TP/IPsec with pre-shared key
Server baldwin.chardma.org.uk
Pre-shared key issued by the platform (the tunnel secret)
Username / password their authentik login
Authentication PAP inside the tunnel (the gateway requires it; IPsec encrypts the wire)

Caveat: Android 12+ removed native L2TP — Windows/macOS/iOS are fine; Android needs a strongSwan-class app (or waits for the WireGuard follow-up).

Building images (tenant side — reference, apply in YOUR repo)

Privileged builds (docker/DinD) are disabled on tenant runners — that isolation is the product. Build with kaniko (rootless), pushing to your own project registry:

# .gitlab-ci.yml — override the Auto DevOps build job, or use standalone
build:
  stage: build
  image:
    name: docker.chardma.org.uk/kaniko-executor:v1.23.2-debug
    entrypoint: [""]
  script:
    - mkdir -p /kaniko/.docker
    - echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf '%s:%s' ${CI_REGISTRY_USER} ${CI_REGISTRY_PASSWORD} | base64 -w0)\"}}}" > /kaniko/.docker/config.json
    - /kaniko/executor
        --context "${CI_PROJECT_DIR}"
        --dockerfile "${CI_PROJECT_DIR}/Dockerfile"
        --destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}"
        --destination "${CI_REGISTRY_IMAGE}:latest"

Using Auto DevOps? Disable its docker-based build and keep the rest:

include:
  - template: Auto-DevOps.gitlab-ci.yml
variables:
  CODE_QUALITY_DISABLED: "true"
build:            # shadow Auto DevOps' DinD build with the kaniko job above
  ...

Notes: the runners are arm64 (build multi-arch or target arm64); jobs get ~2Gi memory (the LimitRange cap) — two builds can run concurrently within the tenant quota.

Cost model (platform side, evolving)

The tenancy’s bound = the ResourceQuota (tunable per tenant in tfvars). Per-tenant costing (PoE wattage + traffic + quota → the Grafana “bill”) is the roadmap item; the quota is the enforcement point today.

Fences (why this is safe to sell)