Nextcloud on Kubernetes without Helm, with Kapitan
Nextcloud 35.0.2 as one replica with its background jobs and an HTTPRoute to a Gateway the cluster already runs, rendered by the kadet-hub kubernetes generator 1.9.0 from Nextcloud’s Helm chart 9.4.1. The database is the included recipe postgresql, the cache and file locking valkey.
- Latest version:
1.0.1, published - Source chart:
oci://ghcr.io/nextcloud/helm/nextcloud:9.4.1@sha256:90ffc4c8dd014e25343ecd37de7262f04d1c2cacb47a48315316d7798e7b35cb - Proof level:
deploys, compared with the chart by the kadet-hub/recipes CI renderspassed in the release run of with check 0.12.0, kind v0.33.0- Output capabilities: none
- Prerequisites, installed once per cluster before the recipe:
gateway-api,cnpg - Generators: kubernetes 1.9.0
sha256:987694d4d8b061a6937fb5b8acd17416a16ed3fea36786a7604e6d607c210e24
What it deploys
recipes.nextcloud(init.yml)recipes.nextcloud.nextcloud(nextcloud.yml):nextcloud
Required parameters
applications.nextcloud.host is the host name of the route and Nextcloud’s
trusted domain. applications.nextcloud.gateway.name and
applications.nextcloud.gateway.namespace name the Gateway the route
attaches to, whose listener must admit routes from the target’s namespace.
applications.nextcloud.admin.password, the admin account’s password, and
applications.valkey.password, which the included recipe valkey
requires, are refs with the backend of your choice. Without one of them the
compile stops with Interpolation key '<key>' not found.
Include
A target includes the recipe and its options as classes and can override anything the recipe sets:
classes:
- recipes.nextcloud
parameters:
namespace: nextcloud
kapitan:
vars:
target: nextcloud
applications:
nextcloud:
host: <host>
admin:
password: <ref>
gateway:
name: <gateway>
namespace: <gateway namespace>
valkey:
password: <ref>
Fetching it
A separate fetch inventory pins the recipe and the recipes it includes by version and digest. This is fetch/targets/recipes.yml for version 1.0.1:
parameters:
kapitan:
dependencies:
- type: oci
source: ghcr.io/kadet-hub/recipes/nextcloud:1.0.1@sha256:dc50173cb04ad7248e76268481a3ddd9ae5c491df836cc7971c68a96bfef2305
output_path: inventory/classes/recipes/nextcloud
- type: oci
source: ghcr.io/kadet-hub/recipes/postgresql:1.0.1@sha256:32c3af2cef6414e9dd57589bf984975b12c525b7c3136615fd5ac2ea7b1f688e
output_path: inventory/classes/recipes/postgresql
- type: oci
source: ghcr.io/kadet-hub/recipes/valkey:1.0.1@sha256:9ba856132f2ed05ab6748d6f23f0ca7df86947593c1dd9fa4b7af21ae3f61e24
output_path: inventory/classes/recipes/valkey
The first command fetches the recipe into inventory/classes/recipes/nextcloud, the second the generators and compiles:
kapitan compile --inventory-path fetch --fetch --output-path .
kapitan compile --fetch
The first command exits with status 1 because the fetch target compiles nothing. Kapitan's --fetch checks neither the digest nor the attestation. This verifies the attestation of each recipe the fetch inventory pins:
gh attestation verify \
oci://ghcr.io/kadet-hub/recipes/nextcloud@sha256:dc50173cb04ad7248e76268481a3ddd9ae5c491df836cc7971c68a96bfef2305 \
--owner kadet-hub \
--cert-identity https://github.com/kadet-hub/registry/.github/workflows/release.yml@refs/heads/main
gh attestation verify \
oci://ghcr.io/kadet-hub/recipes/postgresql@sha256:32c3af2cef6414e9dd57589bf984975b12c525b7c3136615fd5ac2ea7b1f688e \
--owner kadet-hub \
--cert-identity https://github.com/kadet-hub/registry/.github/workflows/release.yml@refs/heads/main
gh attestation verify \
oci://ghcr.io/kadet-hub/recipes/valkey@sha256:9ba856132f2ed05ab6748d6f23f0ca7df86947593c1dd9fa4b7af21ae3f61e24 \
--owner kadet-hub \
--cert-identity https://github.com/kadet-hub/registry/.github/workflows/release.yml@refs/heads/main
The attested index must also list each digest under its recipe with the pinned version, and not as yanked. Verifying by hand shows how to read it.
Settings
| Chart value | Inventory |
|---|---|
image.tag |
components.nextcloud.image, tag and digest |
nextcloud.host, nextcloud.trustedDomains, httpRoute.hostnames |
applications.nextcloud.host |
nextcloud.username |
applications.nextcloud.admin.username, admin by default |
nextcloud.password |
applications.nextcloud.admin.password |
httpRoute.parentRefs |
applications.nextcloud.gateway.name and .namespace, and .section for the listener’s sectionName, unset by default |
persistence.size, persistence.storageClass |
components.nextcloud.persistent_volume_claims.nextcloud-nextcloud.storage, .storage_class |
persistence.nextcloudData.size, persistence.nextcloudData.storageClass |
components.nextcloud.persistent_volume_claims.nextcloud-nextcloud-data.storage, .storage_class |
nextcloud.extraEnv |
components.nextcloud.env, which the cron sidecar gets too |
resources |
components.nextcloud.resources |
nodeSelector, tolerations, affinity |
components.nextcloud.node_selector, components.nextcloud.tolerations, components.nextcloud.affinity |
The database, its owner and the Cluster’s name are settings of
postgresql, which its README lists. Nextcloud reads the host, database
and credentials from the Secret <cluster>-app, where <cluster> is
applications.postgresql.cluster.
The image writes the host into config.php when it installs Nextcloud. A
later change of applications.nextcloud.host also needs
occ config:system:set trusted_domains.
Set TRUSTED_PROXIES in components.nextcloud.env to the addresses of
the Gateway implementation’s proxies. It has no default, and without it
Nextcloud takes the proxy’s address for the client’s.
The image accepts PHP uploads up to PHP_UPLOAD_LIMIT, 512M by default,
set in components.nextcloud.env. Gateway API has no request body limit
of its own, so the Gateway implementation must allow bodies of that size
in its own policy.
The chart’s liveness probe restarts a container that is still installing, and the image installs only on its first start. The recipe therefore runs the chart’s startup probe, which waits up to 330 seconds.
SMTP, notify_push, an office server, imaginary, metrics and more than one replica are not part of this version.
Proof
check nextcloud --level deploys with tests/values.yaml and
tests/target.yml, on kind kindest/node:v1.37.0, with cnpg and
gateway-api installed first. The objects of postgresql and valkey are
left out of the comparison and applied with the recipe. The Deployment and
the Service select on name, the chart on app.kubernetes.io/name,
instance and component. tests/target.yml runs one PostgreSQL
instance.
tests/smoke waits for the Cluster and reads status.php through the
Gateway of check, with the route’s host name, until it reports
"installed":true. It then writes a file over WebDAV as the admin and
reads it back, and checks that occ names Redis as the file locking
backend. The redirects from /.well-known/carddav and
/.well-known/caldav to /remote.php/dav are compared at renders only,
because cloud-provider-kind drops the path of a redirect.
Versions
1.0.1, :sha256:dc50173cb04ad7248e76268481a3ddd9ae5c491df836cc7971c68a96bfef23051.0.0, :sha256:980e5ae6cfe0ed5aafc511ee466d01486d41dc19295385dc940d69e7c92c6f7d
The recipe's files are in kadet-hub/recipes. All recipes