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:
kubectlwith the intended cluster selected. Checkkubectl config current-contextandkubectl cluster-info. Installing CRDs and ClusterRoles requires cluster permissions; routine site editing requires only a browser account.- Controller, frontend and CMS images in a registry reachable by cluster nodes. Replace all three image placeholders in the controller manifest. The CMS image includes the browser editor. For custom images, follow the separate build guide.
- A storage driver supporting ReadWriteMany (RWX) on both volumes, including
cross-node mounts, atomic rename, symlinks, hard links and file/directory sync.
The snapshotter uses hard links to hold immutable media. Check available classes
with
kubectl get storageclass. A class calledstandardis not necessarily RWX. - An installed
networking.k8s.io/v1ingress controller and an appropriate IngressClass (kubectl get ingressclass). Viking creates Ingress objects, not the controller that handles them. - DNS for the public hostname, aliases and a different CMS hostname pointing at that ingress. Viking does not create DNS records.
- TLS certificates for public/CMS ingress. If using cert-manager, install it and configure a working Issuer or ClusterIssuer first. Otherwise provision the certificate Secrets expected by the Ingress objects. TLS blocks alone do not issue certificates, and a default issuer must actually be configured to be used.
- An HTTPS OAuth2/OIDC identity provider with a registered CMS client, or an installed Ipseity. The CMS must reach its endpoints; a plain Kubernetes installation does not require TAK-OS or Ipseity.
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
- Create a site namespace, for example
kubectl create namespace websites-example. - 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. - Create the authentication Secret in websites-example, following
authentication. Generate a
random session key, for example
openssl rand -base64 32. Put that string instringData.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. - Save the following as a local
website.yaml, replacing example domains, theYOUR_RWX_CLASSvalues 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
- 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. - 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.
