Back to Hub

cargo.ayedo.cloud/ayedo/k8s/backplane

Registry block

polycrate block pull cargo.ayedo.cloud/ayedo/k8s/backplane:0.15.3

Backplane Kubernetes Block

Deploy the ayedo Backplane content platform (Django 5 ASGI) to Kubernetes.

The block is public on the Hub (cargo.ayedo.cloud/ayedo/k8s/backplane). The image is internal: cargo.ayedo.cloud/ayedo-internal-tooling/backplane.

Quick start

blocks:
  - name: backplane
    from: cargo.ayedo.cloud/ayedo/k8s/backplane
    config:
      namespace: backplane
      ingress:
        enabled: true
        host: ayedo.de
        www_redirect: true          # TLS SAN www.ayedo.de + HAProxy 301
        extra_hosts:
          - content.ayedo.de        # same backend, extra vhost
          - docs.ayedo.de           # app 301 → /docs/{rest} — not redirect-from
          - docs.ayedo.cloud
          - docs.polycrate.io
      env:
        SECRET_KEY: "…"
        ALLOWED_HOSTS: "ayedo.de,www.ayedo.de,content.ayedo.de,docs.ayedo.de,docs.ayedo.cloud,docs.polycrate.io,icons.ayedo.de,*"
        CSRF_TRUSTED_ORIGINS: "https://ayedo.de,https://www.ayedo.de"
        PUBLIC_CANONICAL_HOST: "ayedo.de"
        DATABASE_URL: "postgres://…"

The image lives on a private registry. For clusters that need pull credentials, set image_credentials.enabled: true plus registry / username / password. The block creates Secret backplane-registry (kubernetes.io/dockerconfigjson) and sets imagePullSecrets on the Deployment and on the migrate/bootstrap Jobs.

Do not bake PUBLIC_CANONICAL_HOST or SITE_URL into the image. Set host-related env at runtime.

ingress.www_redirect: true adds www.{host} as a TLS SAN and haproxy-ingress.github.io/redirect-from (301). That is the permanent www redirect — no extra Ingress rule. extra_hosts adds more vhosts on the same backend (e.g. keep content.ayedo.de after the ayedo.de cutover). Docs aliases (docs.ayedo.de, docs.ayedo.cloud, docs.polycrate.io) must be extra_hosts, not redirect-from: HAProxy would 301 to ayedo.de/{path} and drop the /docs prefix. The app HostRedirectMiddleware prepends /docs/ (and /media/icons/ for icons.ayedo.de) when PUBLIC_CANONICAL_HOST is set.

MCP split

Streamable HTTP sessions live in process RAM (_server_instances). Redis/Valkey cannot share them. Production therefore splits like Dev Caddy:

  • Web (backplane): 3 replicas × 4 uvicorn workers — HTML, media, admin, API
  • MCP (backplane-mcp): 1 replica × --workers 1 — /mcp and /mcp/public

Ingress Prefix /mcp and /mcp/public go to Service backplane-mcp. Everything else stays on backplane. Same image, same Secret backplane-env, same probe Host. After an MCP pod restart, clients must reconnect (mcp_auth). Do not raise MCP replicas without sticky sessions on mcp-session-id and one worker per pod.

Authentik uses the existing client backplane. Redirect URIs (no second client): Dev http://100.64.0.9:8020/api/v1/auth/oidc/callback/, https://content.ayedo.de/api/v1/auth/oidc/callback/, and https://ayedo.de/api/v1/auth/oidc/callback/.

Peer / laser24 env

The peer needs its own secrets. Put them in the workspace block config.env (encrypted workspace) or a cluster Secret — never in the app image.

Required:

  • SECRET_KEY, DATABASE_URL
  • REDIS_URL / VALKEY_URL (Valkey in the same namespace; Django cache sessions)
  • ALLOWED_HOSTS includes the public host plus aliases (ayedo.de,www.ayedo.de,content.ayedo.de,docs.ayedo.de,*)
  • CSRF_TRUSTED_ORIGINS includes https://ayedo.de (and www / content while they still serve)
  • PUBLIC_CANONICAL_HOST / PUBLIC_SITE_URL = ayedo.de after cutover
  • Authentik admin OIDC: AUTHENTIK_ISSUER, AUTHENTIK_AUDIENCE, AUTHENTIK_JWKS_URL, AUTHENTIK_CLIENT_ID, AUTHENTIK_CLIENT_SECRET
  • ChatGPT MCP OAuth (public provider backplane-chatgpt, no secret): AUTHENTIK_CHATGPT_ISSUER, AUTHENTIK_CHATGPT_JWKS_URL, AUTHENTIK_CHATGPT_CLIENT_ID
  • Object storage: S3_MEDIA_ENDPOINT, S3_MEDIA_ACCESS_KEY, S3_MEDIA_SECRET_KEY, S3_MEDIA_BUCKET_NAME, S3_MEDIA_USE_SSL

See examples.poly (test-domain, laser24-peer). Empty strings there are placeholders.

Actions

polycrate run backplane install
polycrate run backplane migrate
polycrate run backplane bootstrap
polycrate run backplane status
polycrate run backplane uninstall

install runs the migrate Job first, then applies Deployment/Service/Ingress. migrate scales the API to 0, runs python manage.py migrate --no-input, then scales back. The container entrypoint does not migrate in Kubernetes (BACKPLANE_MIGRATE_ON_START=false).

Health

  • GET /healthz/live
  • GET /healthz/ready
  • GET /api/v1/health/

Kubelet probes still connect to the pod IP. They send Host from probe_host, else ingress.host, else PUBLIC_CANONICAL_HOST, else the first ALLOWED_HOSTS entry, so Django does not reject the request with Invalid HTTP_HOST header.