Deploying Nextcloud in Kubernetes
This is the first part in my series of posts regarding running Nextcloud and Collabora CODE in Kubernetes.
- Deploying Nextcloud in Kubernetes
- Deploying Collabora CODE in Kubernetes
- Configuring Nextcloud with Collabora Code in Kubernetes
Preparation
I have twelve files that deploy Nextcloud into my cluster to break out the deployment into different steps. These are for the three main component required for a Nextcloud installation:
- PostreSQL Database
- Redis
- Nextcloud
While deploying all of these services, I have a terminal window open and run the following command so that I can monitor the services as the cluster deploys them.
watch -n 3 kubectl get all,pvc,ingress,certificaterequests,orders,certificates,secret,cronjob,configmap -n nextcloud
Deployment
Namespace
Create a namespace for Nextcloud in your Kubernetes cluster.
kubectl create namespace nextcloud
Secrets
I started with a single secrets file, secrets.yml
that contains the secrets data that will be used for all three tools.
apiVersion: v1
kind: Secret
metadata:
name: nextcloud-secrets
namespace: nextcloud
type: Opaque
stringData:
POSTGRES_DB: $DB
POSTGRES_USER: $DB_USER
POSTGRES_PASSWORD: $DB_NEXTCLOUD_PASSWORD
NEXTCLOUD_ADMIN_USER: $NEXTCLOUD_ADMIN_USER
NEXTCLOUD_ADMIN_PASSWORD: $NEXTCLOUD_ADMIN_PASSWORD
Each of the variables is set by an environment variable on my local machine so that the actual secrets are not committed in plain text. Then, I run the following command to deploy the secrets file.
envsubst < secrets.yml | kubectl apply -f -
PostgreSQL
Create three files for PostgreSQL:
postgresql_pvc.yml
postgresql_service.yml
postgresql_deployment.yml
Database storage is provided by Longhorn. I have started with 2 Gigabytes of storage, which Longhorn will allow me to increase in time if the database grows too large. postgresql_pvc.yml
is the following YAML.
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgresql-pvc
namespace: nextcloud
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 2Gi
Next, define the service in postgresql_service.yml
.
---
apiVersion: v1
kind: Service
metadata:
name: postgresql
namespace: nextcloud
labels:
app: postgresql
spec:
ports:
- port: 5432
selector:
app: postgresql
Lastly, configure the deployment postgresql_deployment.yml
.
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgresql
namespace: nextcloud
labels:
app: postgresql
spec:
replicas: 1
selector:
matchLabels:
app: postgresql
template:
metadata:
labels:
app: postgresql
spec:
containers:
- name: postgresql
image: postgres:15
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
valueFrom:
secretKeyRef:
key: POSTGRES_DB
name: nextcloud-secrets
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
key: POSTGRES_USER
name: nextcloud-secrets
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
key: POSTGRES_PASSWORD
name: nextcloud-secrets
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
- name: TZ
value: America/New_York
volumeMounts:
- name: postgresql-data
mountPath: /var/lib/postgresql/data
volumes:
- name: postgresql-data
persistentVolumeClaim:
claimName: postgresql-pvc
Once these files are created, then you can apply them all to your cluster.
kubectl apply -f \
postgresql_pvc.yml \
postgresql_service.yml \
postgresql_deployment.yml
Watch your terminal window until your PostgreSQL pod shows that it is in a Running
state.
Redis
Redis will be setup in a similar fashion. The files are:
redis_service.yml
redis_deployment.yml
Create the redis_service.yml
file as follows.
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: nextcloud
labels:
app: redis
spec:
ports:
- port: 6379
selector:
app: redis
And then the redis_deployment.yml
file.
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: nextcloud
labels:
app: redis
spec:
selector:
matchLabels:
app: redis
replicas: 1
template:
metadata:
labels:
app: redis
spec:
containers:
- image: redis:alpine
name: redis
ports:
- containerPort: 6379
env:
- name: TZ
value: America/New_York
restartPolicy: Always
Once these files are created, then you can apply them all to your cluster.
kubectl apply -f \
redis_service.yml \
redis_deployment.yml
Watch your terminal window until your Redis pod shows that it is in a Running
state.
Nextcloud
This section has a few more deployment files.
File | Purpose |
---|---|
nextcloud_pvc.yml |
Requests storage from Longhorn and NFS provisioners. |
nextcloud_headers.yml |
Sets Traefik to enable HTTP headers that Nextcloud requires. |
nextcloud_service.yml |
Configures the Nextcloud Kubernetes service. |
nextcloud_ingress.yml |
Configures the Kubernetes ingress to the Nextcloud Service. |
nextcloud_deployment.yml |
Configures the Kubernetes deployment of Nextcloud. |
nextcloud_cron.yml |
Sets the web cron job for Nextcloud to run every 5 minutes. |
Create nextcloud_pvc.yml
in order to request storage.
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nextcloud-pvc
namespace: nextcloud
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nextcloud-pvc-nfs
namespace: nextcloud
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs-client
resources:
requests:
storage: 100Gi
Create nextcloud_headers.yml
to create Traefik middleware that will set HTTP headers that Nextcloud cares about, and to enable the .well-known
URL paths for DAV-based services.
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: headers
namespace: nextcloud
spec:
headers:
frameDeny: true
browserXssFilter: true
customResponseHeaders:
Strict-Transport-Security: "15552000"
X-Frame-Options: SAMEORIGIN
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: redirects
namespace: nextcloud
spec:
redirectScheme:
permanent: true
scheme: https
redirectRegex:
regex: https://(.*)/.well-known/(card|cal)dav
replacement: https://$1/remote.php/dav/
Create the nextcloud_service.yml
to setup the service.
---
apiVersion: v1
kind: Service
metadata:
name: nextcloud
namespace: nextcloud
labels:
app: nextcloud
spec:
ports:
- port: 80
selector:
app: nextcloud
The service will operate on port 80, but Traefik was previously configured in the headers file to redirect requests to HTTPS. The ingress configuration will then serve Nextcloud from HTTPS.
Create nextcloud_ingress.yml
with the following. Note that you will need to setup the proper domain for your Nextcloud instance. This configuration assumes that you have a working CertManager deployment on your cluster; as you can see, I am using a cluster issuer that I setup previously called letsencrypt-aws
.
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nextcloud-ingress
namespace: nextcloud
annotations:
traefik.ingress.kubernetes.io/router.middlewares: nextcloud-headers@kubernetescrd,nextcloud-redirects@kubernetescrd
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
cert-manager.io/cluster-issuer: letsencrypt-aws
spec:
rules:
- host: your.nextcloud.domain
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nextcloud
port:
number: 80
tls:
- secretName: ssl-cert
hosts:
- your.nextcloud.domain
Now, we configure the Nextcloud deployment in nextcloud_deployment.yml
.
Note that I use the most-specific tag to call out the latest Nextcloud release, nextcloud:25.0.3-apache
in the example below. If you use a pod management strategy that could download and apply a new version of Nextcloud without a manual trigger, like using the tag nextcloud:25-apache
that will change as new versions are released, then the Nextcloud upgrade process could start unintentionally. That process can take many minutes. I prefer to be alerted by Nextcloud when there are new updates and then scheduling an upgrade when convenient to apply the update.
Be sure to set your domain name in the proper variables.
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nextcloud
namespace: nextcloud
labels:
app: nextcloud
spec:
replicas: 1
selector:
matchLabels:
app: nextcloud
strategy:
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
labels:
app: nextcloud
spec:
containers:
- image: nextcloud:25.0.3-apache
name: nextcloud
ports:
- containerPort: 80
protocol: TCP
env:
- name: REDIS_HOST
value: redis
- name: POSTGRES_HOST
value: postgresql
- name: POSTGRES_DB
valueFrom:
secretKeyRef:
key: POSTGRES_DB
name: nextcloud-secrets
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
key: POSTGRES_USER
name: nextcloud-secrets
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
key: POSTGRES_PASSWORD
name: nextcloud-secrets
- name: NEXTCLOUD_ADMIN_USER
valueFrom:
secretKeyRef:
key: NEXTCLOUD_ADMIN_USER
name: nextcloud-secrets
- name: NEXTCLOUD_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
key: NEXTCLOUD_ADMIN_PASSWORD
name: nextcloud-secrets
- name: NEXTCLOUD_TRUSTED_DOMAINS
value: your.nextcloud.domain
- name: NEXTCLOUD_DATA_DIR
value: /mnt/data
# - name: OBJECTSTORE_S3_HOST
# value: your.s3.host
# - name: OBJECTSTORE_S3_REGION
# value: gso-rack-1
# - name: OBJECTSTORE_S3_BUCKET
# value: nextcloud
# - name: OBJECTSTORE_S3_PORT
# value: "9000"
# - name: OBJECTSTORE_S3_SSL
# value: "true"
# - name: OBJECTSTORE_S3_USEPATH_STYLE
# value: "true"
# - name: OBJECTSTORE_S3_KEY
# valueFrom:
# secretKeyRef:
# key: OBJECTSTORE_S3_KEY
# name: nextcloud-secrets
# - name: OBJECTSTORE_S3_SECRET
# valueFrom:
# secretKeyRef:
# key: OBJECTSTORE_S3_SECRET
# name: nextcloud-secrets
- name: TRUSTED_PROXIES
value: 192.168.4.0/24 10.0.0.0/16 # This includes my router IP address and the CIDR range of the cluster
- name: APACHE_DISABLE_REWRITE_IP
value: "1"
- name: OVERWRITEHOST
value: your.nextcloud.domain
- name: OVERWRITEPROTOCOL
value: https
- name: OVERWRITECLIURL
value: https://your.nextcloud.domain
- name: OVERWRITEWEBROOT
value: "/"
- name: PHP_MEMORY_LIMIT
value: 4G
- name: PHP_UPLOAD_LIMIT
value: 1G
- name: TZ
value: America/New_York
volumeMounts:
- mountPath: /var/www/html
name: nextcloud-storage
readOnly: false
- mountPath: /mnt/data
name: nextcloud-storage-nfs
readOnly: false
volumes:
- name: nextcloud-storage
persistentVolumeClaim:
claimName: nextcloud-pvc
- name: nextcloud-storage-nfs
persistentVolumeClaim:
claimName: nextcloud-pvc-nfs
We then need to setup the Nextcloud CRON job in nextcloud_cron.yml
. The Nextcloud container does not contain the CRON utility, so the web-based cron is the best solution here. I reuse the same Nextcloud container image to run the CRON job as the deployment.
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: nextcloud-cron
namespace: nextcloud
spec:
schedule: "*/5 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: nextcloud
image: nextcloud:25.0.3-apache
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -c
- curl https://your.nextcloud.domain/cron.php
restartPolicy: OnFailure
Now, we need to apply all of these files.
kubectl apply -f \
nextcloud_pvc.yml \
nextcloud_headers.yml \
nextcloud_service.yml \
nextcloud_ingress.yml \
nextcloud_ingress.yml \
nextcloud_deployment.yml \
nextcloud_cron.yml
This process can take about five minutes to spin up all of the resources. In particular, the SSL certificate request can take a minute or two itself. So, be patient at this step and follow the watch
command that you started at the beginning of this post. When all pods are in a Running
state, and your certificates show the ready state as True
, then you should be able to access your nextcloud instance.
Next, we need to deploy Collabora Code.