feat: Adds Nextcloud application

Deploys Nextcloud with an FPM-alpine image and Caddy sidecar for web serving.
Integrates an external CloudNativePG cluster for PostgreSQL database.
Utilizes an external Valkey instance for caching.
Configures S3-compatible object storage for file data.
Includes an initialization job to create admin and Valkey secrets.
Sets up Ingress for external access with TLS via cert-manager.
This commit is contained in:
Felix Wolf 2026-04-04 18:08:13 +02:00
parent 27647e6c5c
commit 034cc213ea
26 changed files with 1553 additions and 0 deletions

View file

@ -0,0 +1,5 @@
#@data/values-schema
---
#@overlay/match-child-defaults missing_ok=True
application:
namespace: nextcloud

View file

@ -14,3 +14,4 @@ environment:
- proto: hcloud-csi - proto: hcloud-csi
- proto: cloudnative-pg - proto: cloudnative-pg
- proto: metrics-server - proto: metrics-server
- proto: nextcloud

View file

@ -0,0 +1,5 @@
#@data/values-schema
---
#@overlay/match-child-defaults missing_ok=True
application:
namespace: nextcloud

View file

@ -0,0 +1,178 @@
---
replicaCount: 2
image:
flavor: fpm-alpine
nginx:
enabled: false
nextcloud:
host: nextcloud.tr1ceracop.de
existingSecret:
enabled: true
secretName: nextcloud-admin-secret
usernameKey: nextcloud-username
passwordKey: nextcloud-password
objectStore:
s3:
enabled: true
bucket: nextcloud-tr1ceracop
host: nbg1.your-objectstorage.com
port: "443"
ssl: true
region: nbg1
usePathStyle: true
existingSecret: nextcloud-s3-credentials
secretKeys:
accessKey: ACCESS_KEY_ID
secretKey: SECRET_ACCESS_KEY
configs:
proxy.config.php: |-
<?php
$CONFIG = array(
'trusted_proxies' => ['10.0.0.0/8'],
'overwriteprotocol' => 'https',
'default_phone_region' => 'DE',
);
phpConfigs:
uploadLimit.ini: |
upload_max_filesize = 16G
post_max_size = 16G
max_input_time = 3600
max_execution_time = 3600
opcache.ini: |
opcache.enable=1
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=10000
opcache.memory_consumption=256
opcache.save_comments=1
opcache.revalidate_freq=60
extraSidecarContainers:
- name: caddy
image: caddy:2-alpine
ports:
- name: http
containerPort: 80
protocol: TCP
volumeMounts:
- name: nextcloud-main
mountPath: /var/www/
subPath: root
- name: nextcloud-main
mountPath: /var/www/html
subPath: html
- name: nextcloud-main
mountPath: /var/www/html/data
subPath: data
- name: nextcloud-main
mountPath: /var/www/html/config
subPath: config
- name: nextcloud-main
mountPath: /var/www/html/custom_apps
subPath: custom_apps
- name: nextcloud-main
mountPath: /var/www/tmp
subPath: tmp
- name: nextcloud-main
mountPath: /var/www/html/themes
subPath: themes
- name: caddy-config
mountPath: /etc/caddy
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
memory: 64Mi
livenessProbe:
httpGet:
path: /status.php
port: 80
httpHeaders:
- name: Host
value: nextcloud.tr1ceracop.de
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /status.php
port: 80
httpHeaders:
- name: Host
value: nextcloud.tr1ceracop.de
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 5
extraVolumes:
- name: caddy-config
configMap:
name: nextcloud-caddy-config
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
internalDatabase:
enabled: false
externalDatabase:
enabled: true
type: postgresql
host: nextcloud-cnpg-rw.nextcloud.svc:5432
database: nextcloud
existingSecret:
enabled: true
secretName: nextcloud-cnpg-app
usernameKey: username
passwordKey: password
mariadb:
enabled: false
postgresql:
enabled: false
redis:
enabled: false
externalRedis:
enabled: true
host: nextcloud-valkey.nextcloud.svc
port: "6379"
existingSecret:
enabled: true
secretName: nextcloud-valkey-password
passwordKey: password
cronjob:
enabled: true
persistence:
enabled: false
ingress:
enabled: true
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt
tls:
- secretName: nextcloud-tls
hosts:
- nextcloud.tr1ceracop.de
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
memory: 512Mi

View file

@ -0,0 +1,16 @@
#@ load("@ytt:data", "data")
#@ app = data.values.application
---
apiVersion: vendir.k14s.io/v1alpha1
kind: Config
directories:
- path: #@ "charts/" + app.name
contents:
- path: .
helmChart:
name: #@ app.name
version: #@ app.version
repository:
url: #@ app.url
lazy: true

View file

@ -0,0 +1,8 @@
#@data/values-schema
---
#@overlay/match-child-defaults missing_ok=True
application:
#! renovate: datasource=helm
name: nextcloud
url: https://nextcloud.github.io/helm/
version: 9.0.4

View file

@ -0,0 +1,85 @@
#@ load("@ytt:data", "data")
#@ ns = data.values.application.namespace
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nextcloud-secret-init
namespace: #@ ns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: nextcloud-secret-init
namespace: #@ ns
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: nextcloud-secret-init
namespace: #@ ns
subjects:
- kind: ServiceAccount
name: nextcloud-secret-init
namespace: #@ ns
roleRef:
kind: Role
name: nextcloud-secret-init
apiGroup: rbac.authorization.k8s.io
---
apiVersion: batch/v1
kind: Job
metadata:
name: nextcloud-secret-init
namespace: #@ ns
annotations:
argocd.argoproj.io/sync-options: Replace=true
spec:
ttlSecondsAfterFinished: 300
template:
spec:
serviceAccountName: nextcloud-secret-init
restartPolicy: OnFailure
containers:
- name: init
image: alpine/k8s:1.32.3
command:
- sh
- -c
- |
set -e
if ! kubectl get secret nextcloud-admin-secret -n ${NAMESPACE} >/dev/null 2>&1; then
PASSWORD=$(head -c 32 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 24)
kubectl create secret generic nextcloud-admin-secret \
-n ${NAMESPACE} \
--from-literal=nextcloud-username=admin \
--from-literal=nextcloud-password="${PASSWORD}"
echo "Created nextcloud-admin-secret"
else
echo "nextcloud-admin-secret already exists, skipping"
fi
if ! kubectl get secret nextcloud-valkey-password -n ${NAMESPACE} >/dev/null 2>&1; then
VALKEY_PASSWORD=$(head -c 32 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 24)
kubectl create secret generic nextcloud-valkey-password \
-n ${NAMESPACE} \
--from-literal=password="${VALKEY_PASSWORD}"
echo "Created nextcloud-valkey-password"
else
echo "nextcloud-valkey-password already exists, skipping"
fi
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace

View file

@ -0,0 +1,61 @@
#@ load("@ytt:data", "data")
#@ ns = data.values.application.namespace
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nextcloud-caddy-config
namespace: #@ ns
data:
Caddyfile: |
:80 {
root * /var/www/html
header {
Referrer-Policy "no-referrer"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
X-Permitted-Cross-Domain-Policies "none"
X-Robots-Tag "noindex, nofollow"
X-XSS-Protection "1; mode=block"
-X-Powered-By
}
redir /.well-known/carddav /remote.php/dav/ 301
redir /.well-known/caldav /remote.php/dav/ 301
redir /.well-known/* /index.php{uri} 301
@blocked path /build/* /tests/* /config/* /lib/* /3rdparty/* /templates/* /data/*
respond @blocked 404
@davclnt {
path /
header User-Agent DavClnt*
}
redir @davclnt /remote.php/webdav/ 302
redir /remote /remote.php{uri} 301
php_fastcgi 127.0.0.1:9000 {
env HTTPS on
env modHeadersAvailable true
env front_controller_active true
resolve_root_symlink
}
@static path *.css *.js *.mjs *.svg *.gif *.ico *.jpg *.png *.webp *.wasm *.tflite *.map *.ogg *.flac
header @static Cache-Control "max-age=15778463"
@fonts path *.otf *.woff *.woff2
header @fonts Cache-Control "max-age=604800"
encode gzip
file_server
request_body {
max_size 16GB
}
}

View file

@ -0,0 +1,36 @@
#@ load("@ytt:data", "data")
#@ ns = data.values.application.namespace
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: nextcloud-cnpg
namespace: #@ ns
spec:
instances: 2
bootstrap:
initdb:
database: nextcloud
owner: nextcloud
storage:
size: 5Gi
storageClass: hcloud-volumes
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 512Mi
postgresql:
parameters:
shared_buffers: "64MB"
effective_cache_size: "128MB"
work_mem: "4MB"
maintenance_work_mem: "32MB"
max_connections: "100"

View file

@ -0,0 +1,18 @@
#@ load("@ytt:data", "data")
#@ load("@ytt:overlay", "overlay")
#@ ns = data.values.application.namespace
---
apiVersion: v1
kind: Namespace
metadata:
name: #@ ns
labels:
pod-security.kubernetes.io/enforce: privileged
#@overlay/match by=overlay.all, expects="1+"
---
metadata:
#@overlay/match missing_ok=True
namespace: #@ ns

View file

@ -0,0 +1,78 @@
#@ load("@ytt:data", "data")
#@ ns = data.values.application.namespace
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nextcloud-valkey
namespace: #@ ns
labels:
app.kubernetes.io/name: valkey
app.kubernetes.io/instance: nextcloud
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: valkey
app.kubernetes.io/instance: nextcloud
template:
metadata:
labels:
app.kubernetes.io/name: valkey
app.kubernetes.io/instance: nextcloud
spec:
containers:
- name: valkey
image: valkey/valkey:8-alpine
args:
- valkey-server
- --requirepass
- $(VALKEY_PASSWORD)
env:
- name: VALKEY_PASSWORD
valueFrom:
secretKeyRef:
name: nextcloud-valkey-password
key: password
ports:
- name: valkey
containerPort: 6379
protocol: TCP
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 128Mi
livenessProbe:
tcpSocket:
port: valkey
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
tcpSocket:
port: valkey
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: nextcloud-valkey
namespace: #@ ns
labels:
app.kubernetes.io/name: valkey
app.kubernetes.io/instance: nextcloud
spec:
type: ClusterIP
ports:
- port: 6379
targetPort: valkey
protocol: TCP
name: valkey
selector:
app.kubernetes.io/name: valkey
app.kubernetes.io/instance: nextcloud

View file

@ -0,0 +1,25 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
annotations:
myks.dev/environment: production
finalizers:
- resources-finalizer.argocd.argoproj.io
name: app-production-nextcloud
namespace: argocd
spec:
destination:
namespace: nextcloud
server: https://kubernetes.default.svc
project: env-production
source:
path: rendered/envs/production/nextcloud
repoURL: ssh://git@git.tr1ceracop.de:222/gitea_admin/k8s-and-chill.git
targetRevision: main
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true

View file

@ -0,0 +1,29 @@
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
annotations:
a8r.io/repository: ssh://git@git.tr1ceracop.de:222/gitea_admin/k8s-and-chill.git
name: nextcloud-cnpg
namespace: nextcloud
spec:
bootstrap:
initdb:
database: nextcloud
owner: nextcloud
instances: 2
postgresql:
parameters:
effective_cache_size: 128MB
maintenance_work_mem: 32MB
max_connections: "100"
shared_buffers: 64MB
work_mem: 4MB
resources:
limits:
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
storage:
size: 5Gi
storageClass: hcloud-volumes

View file

@ -0,0 +1,58 @@
apiVersion: v1
data:
Caddyfile: |
:80 {
root * /var/www/html
header {
Referrer-Policy "no-referrer"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
X-Permitted-Cross-Domain-Policies "none"
X-Robots-Tag "noindex, nofollow"
X-XSS-Protection "1; mode=block"
-X-Powered-By
}
redir /.well-known/carddav /remote.php/dav/ 301
redir /.well-known/caldav /remote.php/dav/ 301
redir /.well-known/* /index.php{uri} 301
@blocked path /build/* /tests/* /config/* /lib/* /3rdparty/* /templates/* /data/*
respond @blocked 404
@davclnt {
path /
header User-Agent DavClnt*
}
redir @davclnt /remote.php/webdav/ 302
redir /remote /remote.php{uri} 301
php_fastcgi 127.0.0.1:9000 {
env HTTPS on
env modHeadersAvailable true
env front_controller_active true
resolve_root_symlink
}
@static path *.css *.js *.mjs *.svg *.gif *.ico *.jpg *.png *.webp *.wasm *.tflite *.map *.ogg *.flac
header @static Cache-Control "max-age=15778463"
@fonts path *.otf *.woff *.woff2
header @fonts Cache-Control "max-age=604800"
encode gzip
file_server
request_body {
max_size 16GB
}
}
kind: ConfigMap
metadata:
annotations:
a8r.io/repository: ssh://git@git.tr1ceracop.de:222/gitea_admin/k8s-and-chill.git
name: nextcloud-caddy-config
namespace: nextcloud

View file

@ -0,0 +1,276 @@
apiVersion: v1
data:
.htaccess: |-
# line below if for Apache 2.4
<ifModule mod_authz_core.c>
Require all denied
</ifModule>
# line below if for Apache 2.2
<ifModule !mod_authz_core.c>
deny from all
</ifModule>
# section for Apache 2.2 and 2.4
<ifModule mod_autoindex.c>
IndexIgnore *
</ifModule>
apache-pretty-urls.config.php: |-
<?php
$CONFIG = array (
'htaccess.RewriteBase' => '/',
);
apcu.config.php: |-
<?php
$CONFIG = array (
'memcache.local' => '\OC\Memcache\APCu',
);
apps.config.php: |-
<?php
$CONFIG = array (
'apps_paths' => array (
0 => array (
'path' => OC::$SERVERROOT.'/apps',
'url' => '/apps',
'writable' => false,
),
1 => array (
'path' => OC::$SERVERROOT.'/custom_apps',
'url' => '/custom_apps',
'writable' => true,
),
),
);
autoconfig.php: |-
<?php
$autoconfig_enabled = false;
if (getenv('SQLITE_DATABASE')) {
$AUTOCONFIG['dbtype'] = 'sqlite';
$AUTOCONFIG['dbname'] = getenv('SQLITE_DATABASE');
$autoconfig_enabled = true;
} elseif (getenv('MYSQL_DATABASE_FILE') && getenv('MYSQL_USER_FILE') && getenv('MYSQL_PASSWORD_FILE') && getenv('MYSQL_HOST')) {
$AUTOCONFIG['dbtype'] = 'mysql';
$AUTOCONFIG['dbname'] = trim(file_get_contents(getenv('MYSQL_DATABASE_FILE')));
$AUTOCONFIG['dbuser'] = trim(file_get_contents(getenv('MYSQL_USER_FILE')));
$AUTOCONFIG['dbpass'] = trim(file_get_contents(getenv('MYSQL_PASSWORD_FILE')));
$AUTOCONFIG['dbhost'] = getenv('MYSQL_HOST');
$autoconfig_enabled = true;
} elseif (getenv('MYSQL_DATABASE') && getenv('MYSQL_USER') && getenv('MYSQL_PASSWORD') && getenv('MYSQL_HOST')) {
$AUTOCONFIG['dbtype'] = 'mysql';
$AUTOCONFIG['dbname'] = getenv('MYSQL_DATABASE');
$AUTOCONFIG['dbuser'] = getenv('MYSQL_USER');
$AUTOCONFIG['dbpass'] = getenv('MYSQL_PASSWORD');
$AUTOCONFIG['dbhost'] = getenv('MYSQL_HOST');
$autoconfig_enabled = true;
} elseif (getenv('POSTGRES_DB_FILE') && getenv('POSTGRES_USER_FILE') && getenv('POSTGRES_PASSWORD_FILE') && getenv('POSTGRES_HOST')) {
$AUTOCONFIG['dbtype'] = 'pgsql';
$AUTOCONFIG['dbname'] = trim(file_get_contents(getenv('POSTGRES_DB_FILE')));
$AUTOCONFIG['dbuser'] = trim(file_get_contents(getenv('POSTGRES_USER_FILE')));
$AUTOCONFIG['dbpass'] = trim(file_get_contents(getenv('POSTGRES_PASSWORD_FILE')));
$AUTOCONFIG['dbhost'] = getenv('POSTGRES_HOST');
$autoconfig_enabled = true;
} elseif (getenv('POSTGRES_DB') && getenv('POSTGRES_USER') && getenv('POSTGRES_PASSWORD') && getenv('POSTGRES_HOST')) {
$AUTOCONFIG['dbtype'] = 'pgsql';
$AUTOCONFIG['dbname'] = getenv('POSTGRES_DB');
$AUTOCONFIG['dbuser'] = getenv('POSTGRES_USER');
$AUTOCONFIG['dbpass'] = getenv('POSTGRES_PASSWORD');
$AUTOCONFIG['dbhost'] = getenv('POSTGRES_HOST');
$autoconfig_enabled = true;
}
if ($autoconfig_enabled) {
$AUTOCONFIG['directory'] = getenv('NEXTCLOUD_DATA_DIR') ?: '/var/www/html/data';
}
helm-metrics.config.php: |-
<?php
$openmetricsAllowedClients = getenv('OPENMETRICS_ALLOWED_CLIENTS');
if ($openmetricsAllowedClients) {
$CONFIG['openmetrics_allowed_clients'] = array_filter(array_map('trim', explode(',', $openmetricsAllowedClients)));
}
proxy.config.php: |-
<?php
$CONFIG = array(
'trusted_proxies' => ['10.0.0.0/8'],
'overwriteprotocol' => 'https',
'default_phone_region' => 'DE',
);
redis.config.php: |-
<?php
if (getenv('REDIS_HOST')) {
$CONFIG = array(
'memcache.distributed' => '\OC\Memcache\Redis',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' => array(
'host' => getenv('REDIS_HOST'),
'password' => getenv('REDIS_HOST_PASSWORD_FILE') ? trim(file_get_contents(getenv('REDIS_HOST_PASSWORD_FILE'))) : (string) getenv('REDIS_HOST_PASSWORD'),
),
);
if (getenv('REDIS_HOST_PORT') !== false) {
$CONFIG['redis']['port'] = (int) getenv('REDIS_HOST_PORT');
} elseif (getenv('REDIS_HOST')[0] != '/') {
$CONFIG['redis']['port'] = 6379;
}
if (getenv('REDIS_HOST_USER') !== false) {
$CONFIG['redis']['user'] = (string) getenv('REDIS_HOST_USER');
}
}
reverse-proxy.config.php: |-
<?php
$overwriteHost = getenv('OVERWRITEHOST');
if ($overwriteHost) {
$CONFIG['overwritehost'] = $overwriteHost;
}
$overwriteProtocol = getenv('OVERWRITEPROTOCOL');
if ($overwriteProtocol) {
$CONFIG['overwriteprotocol'] = $overwriteProtocol;
}
$overwriteCliUrl = getenv('OVERWRITECLIURL');
if ($overwriteCliUrl) {
$CONFIG['overwrite.cli.url'] = $overwriteCliUrl;
}
$overwriteWebRoot = getenv('OVERWRITEWEBROOT');
if ($overwriteWebRoot) {
$CONFIG['overwritewebroot'] = $overwriteWebRoot;
}
$overwriteCondAddr = getenv('OVERWRITECONDADDR');
if ($overwriteCondAddr) {
$CONFIG['overwritecondaddr'] = $overwriteCondAddr;
}
$trustedProxies = getenv('TRUSTED_PROXIES');
if ($trustedProxies) {
$CONFIG['trusted_proxies'] = array_filter(array_map('trim', explode(' ', $trustedProxies)));
}
$forwardedForHeaders = getenv('FORWARDED_FOR_HEADERS');
if ($forwardedForHeaders) {
$CONFIG['forwarded_for_headers'] = array_filter(array_map('trim', explode(' ', $forwardedForHeaders)));
}
s3.config.php: |-
<?php
if (getenv('OBJECTSTORE_S3_BUCKET')) {
$use_ssl = getenv('OBJECTSTORE_S3_SSL');
$use_path = getenv('OBJECTSTORE_S3_USEPATH_STYLE');
$use_legacyauth = getenv('OBJECTSTORE_S3_LEGACYAUTH');
$autocreate = getenv('OBJECTSTORE_S3_AUTOCREATE');
$CONFIG = array(
'objectstore' => array(
'class' => '\OC\Files\ObjectStore\S3',
'arguments' => array(
'bucket' => getenv('OBJECTSTORE_S3_BUCKET'),
'region' => getenv('OBJECTSTORE_S3_REGION') ?: '',
'hostname' => getenv('OBJECTSTORE_S3_HOST') ?: '',
'port' => getenv('OBJECTSTORE_S3_PORT') ?: '',
'storageClass' => getenv('OBJECTSTORE_S3_STORAGE_CLASS') ?: '',
'objectPrefix' => getenv("OBJECTSTORE_S3_OBJECT_PREFIX") ? getenv("OBJECTSTORE_S3_OBJECT_PREFIX") : "urn:oid:",
'autocreate' => strtolower($autocreate) !== 'false',
'use_ssl' => strtolower($use_ssl) !== 'false',
// required for some non Amazon S3 implementations
'use_path_style' => $use_path == true && strtolower($use_path) !== 'false',
// required for older protocol versions
'legacy_auth' => $use_legacyauth == true && strtolower($use_legacyauth) !== 'false'
)
)
);
if (getenv('OBJECTSTORE_S3_KEY_FILE')) {
$CONFIG['objectstore']['arguments']['key'] = trim(file_get_contents(getenv('OBJECTSTORE_S3_KEY_FILE')));
} elseif (getenv('OBJECTSTORE_S3_KEY')) {
$CONFIG['objectstore']['arguments']['key'] = getenv('OBJECTSTORE_S3_KEY');
} else {
$CONFIG['objectstore']['arguments']['key'] = '';
}
if (getenv('OBJECTSTORE_S3_SECRET_FILE')) {
$CONFIG['objectstore']['arguments']['secret'] = trim(file_get_contents(getenv('OBJECTSTORE_S3_SECRET_FILE')));
} elseif (getenv('OBJECTSTORE_S3_SECRET')) {
$CONFIG['objectstore']['arguments']['secret'] = getenv('OBJECTSTORE_S3_SECRET');
} else {
$CONFIG['objectstore']['arguments']['secret'] = '';
}
if (getenv('OBJECTSTORE_S3_SSE_C_KEY_FILE')) {
$CONFIG['objectstore']['arguments']['sse_c_key'] = trim(file_get_contents(getenv('OBJECTSTORE_S3_SSE_C_KEY_FILE')));
} elseif (getenv('OBJECTSTORE_S3_SSE_C_KEY')) {
$CONFIG['objectstore']['arguments']['sse_c_key'] = getenv('OBJECTSTORE_S3_SSE_C_KEY');
}
}
smtp.config.php: |-
<?php
if (getenv('SMTP_HOST') && getenv('MAIL_FROM_ADDRESS') && getenv('MAIL_DOMAIN')) {
$CONFIG = array (
'mail_smtpmode' => 'smtp',
'mail_smtphost' => getenv('SMTP_HOST'),
'mail_smtpport' => getenv('SMTP_PORT') ?: (getenv('SMTP_SECURE') ? 465 : 25),
'mail_smtpsecure' => getenv('SMTP_SECURE') ?: '',
'mail_smtpauth' => getenv('SMTP_NAME') && (getenv('SMTP_PASSWORD') || getenv('SMTP_PASSWORD_FILE')),
'mail_smtpauthtype' => getenv('SMTP_AUTHTYPE') ?: 'LOGIN',
'mail_smtpname' => getenv('SMTP_NAME') ?: '',
'mail_from_address' => getenv('MAIL_FROM_ADDRESS'),
'mail_domain' => getenv('MAIL_DOMAIN'),
);
if (getenv('SMTP_PASSWORD_FILE')) {
$CONFIG['mail_smtppassword'] = trim(file_get_contents(getenv('SMTP_PASSWORD_FILE')));
} elseif (getenv('SMTP_PASSWORD')) {
$CONFIG['mail_smtppassword'] = getenv('SMTP_PASSWORD');
} else {
$CONFIG['mail_smtppassword'] = '';
}
}
swift.config.php: |-
<?php
if (getenv('OBJECTSTORE_SWIFT_URL')) {
$autocreate = getenv('OBJECTSTORE_SWIFT_AUTOCREATE');
$CONFIG = array(
'objectstore' => [
'class' => 'OC\\Files\\ObjectStore\\Swift',
'arguments' => [
'autocreate' => $autocreate == true && strtolower($autocreate) !== 'false',
'user' => [
'name' => getenv('OBJECTSTORE_SWIFT_USER_NAME'),
'password' => getenv('OBJECTSTORE_SWIFT_USER_PASSWORD'),
'domain' => [
'name' => (getenv('OBJECTSTORE_SWIFT_USER_DOMAIN')) ?: 'Default',
],
],
'scope' => [
'project' => [
'name' => getenv('OBJECTSTORE_SWIFT_PROJECT_NAME'),
'domain' => [
'name' => (getenv('OBJECTSTORE_SWIFT_PROJECT_DOMAIN')) ?: 'Default',
],
],
],
'serviceName' => (getenv('OBJECTSTORE_SWIFT_SERVICE_NAME')) ?: 'swift',
'region' => getenv('OBJECTSTORE_SWIFT_REGION'),
'url' => getenv('OBJECTSTORE_SWIFT_URL'),
'bucket' => getenv('OBJECTSTORE_SWIFT_CONTAINER_NAME'),
]
]
);
}
upgrade-disable-web.config.php: |-
<?php
$CONFIG = array (
'upgrade.disable-web' => true,
);
kind: ConfigMap
metadata:
annotations:
a8r.io/repository: ssh://git@git.tr1ceracop.de:222/gitea_admin/k8s-and-chill.git
labels:
app.kubernetes.io/instance: nextcloud
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: nextcloud
app.kubernetes.io/version: 33.0.0
helm.sh/chart: nextcloud-9.0.4
name: nextcloud-config
namespace: nextcloud

View file

@ -0,0 +1,26 @@
apiVersion: v1
data:
opcache.ini: |-
opcache.enable=1
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=10000
opcache.memory_consumption=256
opcache.save_comments=1
opcache.revalidate_freq=60
uploadLimit.ini: |-
upload_max_filesize = 16G
post_max_size = 16G
max_input_time = 3600
max_execution_time = 3600
kind: ConfigMap
metadata:
annotations:
a8r.io/repository: ssh://git@git.tr1ceracop.de:222/gitea_admin/k8s-and-chill.git
labels:
app.kubernetes.io/instance: nextcloud
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: nextcloud
app.kubernetes.io/version: 33.0.0
helm.sh/chart: nextcloud-9.0.4
name: nextcloud-phpconfig
namespace: nextcloud

View file

@ -0,0 +1,55 @@
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
a8r.io/repository: ssh://git@git.tr1ceracop.de:222/gitea_admin/k8s-and-chill.git
labels:
app.kubernetes.io/instance: nextcloud
app.kubernetes.io/name: valkey
name: nextcloud-valkey
namespace: nextcloud
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/instance: nextcloud
app.kubernetes.io/name: valkey
template:
metadata:
labels:
app.kubernetes.io/instance: nextcloud
app.kubernetes.io/name: valkey
spec:
containers:
- args:
- valkey-server
- --requirepass
- $(VALKEY_PASSWORD)
env:
- name: VALKEY_PASSWORD
valueFrom:
secretKeyRef:
key: password
name: nextcloud-valkey-password
image: valkey/valkey:8-alpine
livenessProbe:
initialDelaySeconds: 10
periodSeconds: 10
tcpSocket:
port: valkey
name: valkey
ports:
- containerPort: 6379
name: valkey
protocol: TCP
readinessProbe:
initialDelaySeconds: 5
periodSeconds: 5
tcpSocket:
port: valkey
resources:
limits:
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi

View file

@ -0,0 +1,421 @@
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
a8r.io/repository: ssh://git@git.tr1ceracop.de:222/gitea_admin/k8s-and-chill.git
labels:
app.kubernetes.io/component: app
app.kubernetes.io/instance: nextcloud
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: nextcloud
app.kubernetes.io/version: 33.0.0
helm.sh/chart: nextcloud-9.0.4
name: nextcloud
namespace: nextcloud
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/component: app
app.kubernetes.io/instance: nextcloud
app.kubernetes.io/name: nextcloud
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
annotations:
hooks-hash: 9525c2748a6c7cd0e28ec740623d0b3fa5a75c83b51ccfd136bc89c76737b204
nextcloud-config-hash: 0d229123637fe9f641f41261891a44b761ff32c52f973314650897e768f3456d
php-config-hash: b638f66fd8d65de8364dbad6efc59a6524c7b2e2377b5623cf5e921e4d3d2400
labels:
app.kubernetes.io/component: app
app.kubernetes.io/instance: nextcloud
app.kubernetes.io/name: nextcloud
spec:
containers:
- env:
- name: POSTGRES_HOST
value: nextcloud-cnpg-rw.nextcloud.svc:5432
- name: POSTGRES_DB
value: nextcloud
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
key: username
name: nextcloud-cnpg-app
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
key: password
name: nextcloud-cnpg-app
- name: NEXTCLOUD_ADMIN_USER
valueFrom:
secretKeyRef:
key: nextcloud-username
name: nextcloud-admin-secret
- name: NEXTCLOUD_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
key: nextcloud-password
name: nextcloud-admin-secret
- name: NEXTCLOUD_TRUSTED_DOMAINS
value: nextcloud.tr1ceracop.de
- name: OPENMETRICS_ALLOWED_CLIENTS
value: 127.0.0.1,10.42.0.0/16,10.43.0.0/16
- name: NEXTCLOUD_DATA_DIR
value: /var/www/html/data
- name: REDIS_HOST
value: nextcloud-valkey.nextcloud.svc
- name: REDIS_HOST_PORT
value: "6379"
- name: REDIS_HOST_PASSWORD
valueFrom:
secretKeyRef:
key: password
name: nextcloud-valkey-password
- name: OBJECTSTORE_S3_SSL
value: "true"
- name: OBJECTSTORE_S3_USEPATH_STYLE
value: "true"
- name: OBJECTSTORE_S3_AUTOCREATE
value: "false"
- name: OBJECTSTORE_S3_REGION
value: nbg1
- name: OBJECTSTORE_S3_PORT
value: "443"
- name: OBJECTSTORE_S3_STORAGE_CLASS
value: STANDARD
- name: OBJECTSTORE_S3_HOST
value: nbg1.your-objectstorage.com
- name: OBJECTSTORE_S3_BUCKET
value: nextcloud-tr1ceracop
- name: OBJECTSTORE_S3_KEY
valueFrom:
secretKeyRef:
key: ACCESS_KEY_ID
name: nextcloud-s3-credentials
- name: OBJECTSTORE_S3_SECRET
valueFrom:
secretKeyRef:
key: SECRET_ACCESS_KEY
name: nextcloud-s3-credentials
- name: OBJECTSTORE_S3_SSE_C_KEY
value: ""
image: docker.io/library/nextcloud:33.0.0-fpm-alpine
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
httpHeaders:
- name: Host
value: nextcloud.tr1ceracop.de
path: /status.php
port: 80
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
name: nextcloud
ports:
- containerPort: 80
name: http
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
httpHeaders:
- name: Host
value: nextcloud.tr1ceracop.de
path: /status.php
port: 80
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
resources:
limits:
memory: 512Mi
requests:
cpu: 200m
memory: 256Mi
volumeMounts:
- mountPath: /var/www/
name: nextcloud-main
subPath: root
- mountPath: /var/www/html
name: nextcloud-main
subPath: html
- mountPath: /var/www/html/data
name: nextcloud-main
subPath: data
- mountPath: /var/www/html/config
name: nextcloud-main
subPath: config
- mountPath: /var/www/html/custom_apps
name: nextcloud-main
subPath: custom_apps
- mountPath: /var/www/tmp
name: nextcloud-main
subPath: tmp
- mountPath: /var/www/html/themes
name: nextcloud-main
subPath: themes
- mountPath: /var/www/html/config/proxy.config.php
name: nextcloud-config
subPath: proxy.config.php
- mountPath: /var/www/html/config/.htaccess
name: nextcloud-config
subPath: .htaccess
- mountPath: /var/www/html/config/apache-pretty-urls.config.php
name: nextcloud-config
subPath: apache-pretty-urls.config.php
- mountPath: /var/www/html/config/apcu.config.php
name: nextcloud-config
subPath: apcu.config.php
- mountPath: /var/www/html/config/apps.config.php
name: nextcloud-config
subPath: apps.config.php
- mountPath: /var/www/html/config/autoconfig.php
name: nextcloud-config
subPath: autoconfig.php
- mountPath: /var/www/html/config/helm-metrics.config.php
name: nextcloud-config
subPath: helm-metrics.config.php
- mountPath: /var/www/html/config/redis.config.php
name: nextcloud-config
subPath: redis.config.php
- mountPath: /var/www/html/config/reverse-proxy.config.php
name: nextcloud-config
subPath: reverse-proxy.config.php
- mountPath: /var/www/html/config/s3.config.php
name: nextcloud-config
subPath: s3.config.php
- mountPath: /var/www/html/config/smtp.config.php
name: nextcloud-config
subPath: smtp.config.php
- mountPath: /var/www/html/config/swift.config.php
name: nextcloud-config
subPath: swift.config.php
- mountPath: /var/www/html/config/upgrade-disable-web.config.php
name: nextcloud-config
subPath: upgrade-disable-web.config.php
- mountPath: /usr/local/etc/php/conf.d/opcache.ini
name: nextcloud-phpconfig
subPath: opcache.ini
- mountPath: /usr/local/etc/php/conf.d/uploadLimit.ini
name: nextcloud-phpconfig
subPath: uploadLimit.ini
- command:
- /cron.sh
env:
- name: POSTGRES_HOST
value: nextcloud-cnpg-rw.nextcloud.svc:5432
- name: POSTGRES_DB
value: nextcloud
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
key: username
name: nextcloud-cnpg-app
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
key: password
name: nextcloud-cnpg-app
- name: NEXTCLOUD_ADMIN_USER
valueFrom:
secretKeyRef:
key: nextcloud-username
name: nextcloud-admin-secret
- name: NEXTCLOUD_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
key: nextcloud-password
name: nextcloud-admin-secret
- name: NEXTCLOUD_TRUSTED_DOMAINS
value: nextcloud.tr1ceracop.de
- name: OPENMETRICS_ALLOWED_CLIENTS
value: 127.0.0.1,10.42.0.0/16,10.43.0.0/16
- name: NEXTCLOUD_DATA_DIR
value: /var/www/html/data
- name: REDIS_HOST
value: nextcloud-valkey.nextcloud.svc
- name: REDIS_HOST_PORT
value: "6379"
- name: REDIS_HOST_PASSWORD
valueFrom:
secretKeyRef:
key: password
name: nextcloud-valkey-password
- name: OBJECTSTORE_S3_SSL
value: "true"
- name: OBJECTSTORE_S3_USEPATH_STYLE
value: "true"
- name: OBJECTSTORE_S3_AUTOCREATE
value: "false"
- name: OBJECTSTORE_S3_REGION
value: nbg1
- name: OBJECTSTORE_S3_PORT
value: "443"
- name: OBJECTSTORE_S3_STORAGE_CLASS
value: STANDARD
- name: OBJECTSTORE_S3_HOST
value: nbg1.your-objectstorage.com
- name: OBJECTSTORE_S3_BUCKET
value: nextcloud-tr1ceracop
- name: OBJECTSTORE_S3_KEY
valueFrom:
secretKeyRef:
key: ACCESS_KEY_ID
name: nextcloud-s3-credentials
- name: OBJECTSTORE_S3_SECRET
valueFrom:
secretKeyRef:
key: SECRET_ACCESS_KEY
name: nextcloud-s3-credentials
- name: OBJECTSTORE_S3_SSE_C_KEY
value: ""
image: docker.io/library/nextcloud:33.0.0-fpm-alpine
imagePullPolicy: IfNotPresent
name: nextcloud-cron
resources: {}
volumeMounts:
- mountPath: /var/www/
name: nextcloud-main
subPath: root
- mountPath: /var/www/html
name: nextcloud-main
subPath: html
- mountPath: /var/www/html/data
name: nextcloud-main
subPath: data
- mountPath: /var/www/html/config
name: nextcloud-main
subPath: config
- mountPath: /var/www/html/custom_apps
name: nextcloud-main
subPath: custom_apps
- mountPath: /var/www/tmp
name: nextcloud-main
subPath: tmp
- mountPath: /var/www/html/themes
name: nextcloud-main
subPath: themes
- mountPath: /var/www/html/config/proxy.config.php
name: nextcloud-config
subPath: proxy.config.php
- mountPath: /var/www/html/config/.htaccess
name: nextcloud-config
subPath: .htaccess
- mountPath: /var/www/html/config/apache-pretty-urls.config.php
name: nextcloud-config
subPath: apache-pretty-urls.config.php
- mountPath: /var/www/html/config/apcu.config.php
name: nextcloud-config
subPath: apcu.config.php
- mountPath: /var/www/html/config/apps.config.php
name: nextcloud-config
subPath: apps.config.php
- mountPath: /var/www/html/config/autoconfig.php
name: nextcloud-config
subPath: autoconfig.php
- mountPath: /var/www/html/config/helm-metrics.config.php
name: nextcloud-config
subPath: helm-metrics.config.php
- mountPath: /var/www/html/config/redis.config.php
name: nextcloud-config
subPath: redis.config.php
- mountPath: /var/www/html/config/reverse-proxy.config.php
name: nextcloud-config
subPath: reverse-proxy.config.php
- mountPath: /var/www/html/config/s3.config.php
name: nextcloud-config
subPath: s3.config.php
- mountPath: /var/www/html/config/smtp.config.php
name: nextcloud-config
subPath: smtp.config.php
- mountPath: /var/www/html/config/swift.config.php
name: nextcloud-config
subPath: swift.config.php
- mountPath: /var/www/html/config/upgrade-disable-web.config.php
name: nextcloud-config
subPath: upgrade-disable-web.config.php
- mountPath: /usr/local/etc/php/conf.d/opcache.ini
name: nextcloud-phpconfig
subPath: opcache.ini
- mountPath: /usr/local/etc/php/conf.d/uploadLimit.ini
name: nextcloud-phpconfig
subPath: uploadLimit.ini
- image: caddy:2-alpine
livenessProbe:
httpGet:
httpHeaders:
- name: Host
value: nextcloud.tr1ceracop.de
path: /status.php
port: 80
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 5
name: caddy
ports:
- containerPort: 80
name: http
protocol: TCP
readinessProbe:
httpGet:
httpHeaders:
- name: Host
value: nextcloud.tr1ceracop.de
path: /status.php
port: 80
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 5
resources:
limits:
memory: 64Mi
requests:
cpu: 50m
memory: 32Mi
volumeMounts:
- mountPath: /var/www/
name: nextcloud-main
subPath: root
- mountPath: /var/www/html
name: nextcloud-main
subPath: html
- mountPath: /var/www/html/data
name: nextcloud-main
subPath: data
- mountPath: /var/www/html/config
name: nextcloud-main
subPath: config
- mountPath: /var/www/html/custom_apps
name: nextcloud-main
subPath: custom_apps
- mountPath: /var/www/tmp
name: nextcloud-main
subPath: tmp
- mountPath: /var/www/html/themes
name: nextcloud-main
subPath: themes
- mountPath: /etc/caddy
name: caddy-config
securityContext:
fsGroup: 33
volumes:
- emptyDir: {}
name: nextcloud-main
- configMap:
name: nextcloud-config
name: nextcloud-config
- configMap:
name: nextcloud-phpconfig
name: nextcloud-phpconfig
- configMap:
name: nextcloud-caddy-config
name: caddy-config

View file

@ -0,0 +1,32 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
a8r.io/repository: ssh://git@git.tr1ceracop.de:222/gitea_admin/k8s-and-chill.git
cert-manager.io/cluster-issuer: letsencrypt
labels:
app.kubernetes.io/component: app
app.kubernetes.io/instance: nextcloud
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: nextcloud
app.kubernetes.io/version: 33.0.0
helm.sh/chart: nextcloud-9.0.4
name: nextcloud
namespace: nextcloud
spec:
ingressClassName: traefik
rules:
- host: nextcloud.tr1ceracop.de
http:
paths:
- backend:
service:
name: nextcloud
port:
number: 8080
path: /
pathType: Prefix
tls:
- hosts:
- nextcloud.tr1ceracop.de
secretName: nextcloud-tls

View file

@ -0,0 +1,48 @@
apiVersion: batch/v1
kind: Job
metadata:
annotations:
a8r.io/repository: ssh://git@git.tr1ceracop.de:222/gitea_admin/k8s-and-chill.git
argocd.argoproj.io/sync-options: Replace=true
name: nextcloud-secret-init
namespace: nextcloud
spec:
template:
spec:
containers:
- command:
- sh
- -c
- |
set -e
if ! kubectl get secret nextcloud-admin-secret -n ${NAMESPACE} >/dev/null 2>&1; then
PASSWORD=$(head -c 32 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 24)
kubectl create secret generic nextcloud-admin-secret \
-n ${NAMESPACE} \
--from-literal=nextcloud-username=admin \
--from-literal=nextcloud-password="${PASSWORD}"
echo "Created nextcloud-admin-secret"
else
echo "nextcloud-admin-secret already exists, skipping"
fi
if ! kubectl get secret nextcloud-valkey-password -n ${NAMESPACE} >/dev/null 2>&1; then
VALKEY_PASSWORD=$(head -c 32 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 24)
kubectl create secret generic nextcloud-valkey-password \
-n ${NAMESPACE} \
--from-literal=password="${VALKEY_PASSWORD}"
echo "Created nextcloud-valkey-password"
else
echo "nextcloud-valkey-password already exists, skipping"
fi
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: alpine/k8s:1.32.3
name: init
restartPolicy: OnFailure
serviceAccountName: nextcloud-secret-init
ttlSecondsAfterFinished: 300

View file

@ -0,0 +1,9 @@
apiVersion: v1
kind: Namespace
metadata:
annotations:
a8r.io/repository: ssh://git@git.tr1ceracop.de:222/gitea_admin/k8s-and-chill.git
labels:
pod-security.kubernetes.io/enforce: privileged
name: nextcloud
namespace: nextcloud

View file

@ -0,0 +1,15 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations:
a8r.io/repository: ssh://git@git.tr1ceracop.de:222/gitea_admin/k8s-and-chill.git
name: nextcloud-secret-init
namespace: nextcloud
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- create

View file

@ -0,0 +1,15 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations:
a8r.io/repository: ssh://git@git.tr1ceracop.de:222/gitea_admin/k8s-and-chill.git
name: nextcloud-secret-init
namespace: nextcloud
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: nextcloud-secret-init
subjects:
- kind: ServiceAccount
name: nextcloud-secret-init
namespace: nextcloud

View file

@ -0,0 +1,20 @@
apiVersion: v1
kind: Service
metadata:
annotations:
a8r.io/repository: ssh://git@git.tr1ceracop.de:222/gitea_admin/k8s-and-chill.git
labels:
app.kubernetes.io/instance: nextcloud
app.kubernetes.io/name: valkey
name: nextcloud-valkey
namespace: nextcloud
spec:
ports:
- name: valkey
port: 6379
protocol: TCP
targetPort: valkey
selector:
app.kubernetes.io/instance: nextcloud
app.kubernetes.io/name: valkey
type: ClusterIP

View file

@ -0,0 +1,26 @@
apiVersion: v1
kind: Service
metadata:
annotations:
a8r.io/repository: ssh://git@git.tr1ceracop.de:222/gitea_admin/k8s-and-chill.git
labels:
app.kubernetes.io/component: app
app.kubernetes.io/instance: nextcloud
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/monitor: enabled
app.kubernetes.io/name: nextcloud
app.kubernetes.io/version: 33.0.0
helm.sh/chart: nextcloud-9.0.4
name: nextcloud
namespace: nextcloud
spec:
ports:
- name: http
port: 8080
protocol: TCP
targetPort: 80
selector:
app.kubernetes.io/component: app
app.kubernetes.io/instance: nextcloud
app.kubernetes.io/name: nextcloud
type: ClusterIP

View file

@ -0,0 +1,7 @@
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
a8r.io/repository: ssh://git@git.tr1ceracop.de:222/gitea_admin/k8s-and-chill.git
name: nextcloud-secret-init
namespace: nextcloud