Content Management that can withstand the plundering hoards

Install Viking CMS on Kubernetes

Run commands in this guide from the repository root on an administrator's workstation. The commands create cluster resources; this guide does not install Kubernetes, a storage driver, an ingress controller or an identity provider. Use the glossary for the terms below and configuration for all component flags.

Prepare the environment

You need:

The namespace's admission/security policies must permit the CMS volume-preparation init container to run as root with CHOWN capability; the app container stays non-root. Restricted policies that forbid that init step need a reviewed platform exception or a supported storage/deployment adaptation before installation.

The public frontend and CMS are separate origins. Do not choose a CMS hostname that equals a public hostname/alias. Admin ports must remain internal. Container registry authentication is configured through your cluster platform.

Install the core resources in order

The tracked manifests contain example image names. Make a working copy:

viking_install_dir=$(mktemp -d)
cp deploy/*.yaml "$viking_install_dir/"

Keep this shell open so viking_install_dir remains set. Edit $viking_install_dir/03-operator.yaml, replacing the controller image and both --frontend-image / --cms-image arguments with real, compatible release tags or immutable image digests. Keep the viking-cms namespace consistent across Deployment, ServiceAccount and ClusterRoleBinding if changing it.

kubectl apply -f "$viking_install_dir/00-namespace.yaml"
kubectl apply -f "$viking_install_dir/01-crd.yaml"
kubectl wait --for=condition=Established --timeout=60s crd/websites.cms.tlspu.com
kubectl apply -f "$viking_install_dir/02-operator-rbac.yaml"
kubectl apply -f "$viking_install_dir/03-operator.yaml"
kubectl rollout status deployment/viking-cms-controller -n viking-cms --timeout=180s

The CRD teaches Kubernetes the Website resource. RBAC gives the operator the permissions to create/manage site infrastructure. The Deployment starts the operator. kubectl apply -f deploy/ is a convenient non-recursive core apply once placeholders are replaced, but the explicit order above makes the CRD readiness boundary clear. Do not recursively apply deploy/examples: they contain illustrative sites and credentials.

Create a site with explicit authentication

  1. Create a site namespace, for example kubectl create namespace websites-example.
  2. Register a client at the IdP with callback https://cms.example.net/oauth2/callback. Obtain its client ID, secret (unless using a public PKCE client) and HTTPS discovery issuer URL.
  3. Create the authentication Secret in websites-example, following authentication. Generate a random session key, for example openssl rand -base64 32. Put that string in stringData.jwtSecret; Kubernetes handles the outer Secret encoding. Save the completed manifest securely outside the repository, then apply it. The shipped Secret example is intentionally unusable without edits.
  4. Save the following as a local website.yaml, replacing example domains, the YOUR_RWX_CLASS values and IngressClass. The same Secret name must be used in both manifests.
apiVersion: cms.tlspu.com/v1alpha1
kind: Website
metadata:
  name: example
  namespace: websites-example
spec:
  domain: example.com
  aliases: [www]
  cms:
    hostname: cms.example.net
    authentication:
      secretRef:
        name: example-cms-oauth2
  storage:
    published:
      size: 5Gi
      storageClassName: YOUR_RWX_CLASS
    private:
      size: 2Gi
      storageClassName: YOUR_RWX_CLASS
  publishing:
    retainReleases: 1
  frontend:
    replicas: 2
  ingress:
    className: YOUR_INGRESS_CLASS
    tls: true
    # Set one issuer field if cert-manager needs explicit selection:
    # cluster_issuer: letsencrypt-prod
  1. Apply with kubectl apply -f website.yaml. The operator creates same-namespace PVCs, frontend/CMS Deployments, Services, Ingresses and the CMS status Role. The Secret is mounted read-only. The CMS authenticates requests itself.
  2. Follow the checks below, then give the CMS/public addresses to the site owner.

The fuller example Website uses viking-cms as its site namespace; do not mix it with the namespace in this guide without editing both the Website and Secret.

Alternative: automatic Ipseity or frontend only

For automatic identity setup, apply a Website without explicit authentication as shown in Ipseity. It discovers the provider, defaults the CMS hostname to cms.<domain> and grants initial administrator membership. Review the default administrator realm/subject before installation. Namespace boundaries matter because Websites in one namespace share an Ipseity realm.

For an intentional frontend-only site, set spec.cms.enabled: false and omit CMS hostname/authentication. You must supply a valid published filesystem through your own process; no CMS is available to produce the first release. Without Ipseity, an omitted CMS configuration is also frontend-only. Explicitly requested CMS access without usable authentication is blocked.

Verify before handing over

kubectl get website example -n websites-example -o yaml
kubectl get deploy,pod,svc,ingress,pvc -n websites-example
kubectl describe website example -n websites-example
kubectl rollout status deployment/example-cms -n websites-example --timeout=180s
kubectl port-forward -n websites-example deployment/example-cms 18081:8081

In another terminal, curl -i http://127.0.0.1:18081/readyz should return 200, JSON status ready and Server: Viking CMS <version>. Stop port-forward with Ctrl+C. Check that the CMS hostname has a valid certificate and redirects an unauthenticated browser to the IdP. An unauthenticated /api/me should return 401, never editable project data.

The frontend is not ready before the first publication. Do not wait for Website Ready or frontend readiness before opening the CMS. Sign in, create /, save and publish using getting started. Then check the public site and frontend rollout, and inspect status.currentRelease and Website conditions. A controller health 200 alone does not prove a site is ready. Use troubleshooting for failures.

Orbit Deploy

The preferred production deployment is maintained in the separate orbit-deployments repository. This checkout can have a gitignored orbit-deployments symlink; it is not a bundled prerequisite. There is no deploy/orbit-deploy-example/ directory in this repository. Coordinate image mapping and rollout with that repository's own instructions; the resources here are the portable reference. Synchronize its CRD copy using schema upgrade instructions.

Storage retention and removal

Deleting a Website removes generated serving/editor workloads but retains PVCs. Explicit authentication Secrets remain user-owned; automatic auth Secrets are removed, while Ipseity realm labels/client/membership ConfigMaps are retained. Take a backup, review retained identities and only remove storage deliberately. Deleting a PVC can delete its backing volume depending on the storage class/PV reclaim policy. Website PVC retention does not change that underlying Kubernetes policy. Do not delete the CRD to remove one site.