Nextcloud + Kubernetes

Hello! My name is Nick and I'm going to show you how to use Kubernetes to self-host your own Nextcloud server.

While the primary purpose of this article is to give you a crash course in Kubernetes, I want to start by answering the question, “What is Nextcloud?” Well, Nextcloud is like Dropbox. It allows you to store files in the cloud, to share files with other people, and to collaborate with co-workers. Plus, it's free and open-source.

Source Code (Optional)

If you want to get your hands on the code, click on the Github link below. Be sure to git checkout the nextcloud tag to view the finished code. Note that in future posts, I will add additional projects to the repo and making changes that aren't reflected in this post.

nick-true-dev/usable-k8s-projects
Three projects that showcase Kubernetes. Project 1 uses K8s to host a Nextcloud server. Project 2 uses K8s to host a Huginn server. And project 3 uses K8s to host a MonicaHQ server. - nick-true-dev...

Set Up Minikube and Kubectl

Since we are going to run Kubernetes on our local machine, I’m going to install Minikube, the single-node development cluster. If you don’t already have Minikube installed, go here: https://kubernetes.io/docs/tasks/tools/install-minikube

Make sure that you also install Kubectl, the command line utility for managing your Kubernetes cluster.

Once Minikube is installed, start it up using the command minikube start.

Define the MySQL Deployment

Nextcloud is made up of two main components: a server container and a database container. Let's start by going over the database.

nextcloud-db-1-3

What we've got here is a config file for a Kubernetes Deployment named nextcloud-db.

nextcloud-db-1a-1

The purpose of this deployment is to make sure that exactly one DB Pod is running at all times.

nextcloud-db-1b-1

If the pod dies or gets evicted from its host node, the deployment will create a replacement pod.

Then what is a Kubernetes pod? Well, it’s basically a “home” for a container. In Kubernetes, containers always live inside of a pod. In this example, the pod is home to a container named mysql which is created from the mysql:5.7 Docker image.

nextcloud-db-1c-1

So to recap: the MySQL DB runs in a container, that container runs in a pod, and the DB pod is created and managed by our deployment. And what do we get from all of this? Well, we get a DB which is more resilient than it otherwise would be.

So far so good. But this deployment config file is missing two important things: some MySQL environment variables, and a stable place to store the DB data. Let's start by going over the database issue.

Kubernetes Volumes

Here's the problem: when a container terminates or crashes, everything stored in the container and it's file system is gone for good. Therefore, we don't want to store the database data on the mysql container. So, we are going to store the data on a Kubernetes Volume. You see, volumes are designed to provide durable storage to containers.

Okay, here's how we set up the volume for the mysql container. First we need a Persistent Volume Claim, otherwise known as a PVC.

nextcloud-shared-pvc

This one requests 256Mi of persistent storage. The cool thing is that the details of “where” and “how” are completely abstract away from us. We don't need to worry about them.

Now let's add the volume configuration code to the mysql container in the deployment config file.

nextcloud-db-2-2

In this bottom section under the volumes field, we have what amounts to a volume declaration.

nextcloud-db-2a

It's kind of like a variable declaration in C or Java, but instead of declaring a variable, we are declaring a volume named db-storage.

Under the persistentVolumeClaim field, we reference the nextcloud-db PVC.

nextcloud-db-2b

The volumeMounts field specifies the directory where the volume gets mounted onto the container’s file system. This means that all files saved in /var/lib/mysql are actually stored in our volume instead of on the container’s filesystem.

Environment Variables and Secrets

The config file is missing some key MySQL environment variables. Let’s start by declaring the MYSQL_DATABASE=nextcloud environment variable. This sets the name of the DB.

nextcloud-db-3

Technically, we could use the same syntax to declare the MYSQL_ROOT_PASSWORD. But this is a bad idea because Kubernetes config files are usually stored in a version control system. And you really shouldn’t store raw passwords and other sensitive data in your VCS.

Instead, we are going to put the sensitive environment variables into a Kubernetes Secret Object. Secret objects are specifically designed to store things like passwords and API keys.

The easiest way to create a secret object is by using the kubectl create secret command. Here’s what that looks like:

kubectl create secret generic nextcloud-db-secret \
    --from-literal=MYSQL_ROOT_PASSWORD=rootPassw0rd1 \
    --from-literal=MYSQL_USER=nextcloud \
    --from-literal=MYSQL_PASSWORD=passw0rd1

This creates a secret named nextcloud-db-secret with three key-value pairs:

  1. MYSQL_ROOT_PASSWORD=rootPassw0rd1
  2. MYSQL_USER=nextcloud
  3. MYSQL_PASSWORD=passw0rd1

After we create the secret, we need to extract the key-value pairs from it and turn them into environment variables on our database container. Add the following code to the mysql container to do just that.

nextcloud-db-4

Now our database deployment is ready to go.

Define the MySQL Service

We also need to define a Kubernetes Service to ensure reliable communications with the DB pod.

A service is a type of component which acts as a communications middleman between a pod and its clients. This way, a pod’s clients don't need to worry about whether they are talking with the original pod or a replacement pod with a different address.

This service sits in front of the pod(s) created by our deployment.

nextcloud-db-5

Any packets that the service receives on port 3306 will get sent to the DB pod’s port 3306. (In case you didn’t already know this, 3306 is the default MySQL port.)

Multiple YAML Documents, One File

Notice that the triple Dash line separates the deployment from the service.

nextcloud-db-5a

These three dashes signify the start of a new YAML document. Thus, you can have multiple component config docs in a single file.

Create the MySQL Database Components

We are now ready to create the database components. For context, here are the files that we've been working with.

term-1

First, create the persistent volume claim:

kubectl apply -f nextcloud-shared-pvc.yaml

Second, create the DB deployment and service:

kubectl apply -f nextcloud-db.yaml

Note that while it will only take a second or two to create the deployment and service, the database probably won't be running yet. The deployment needs to create the DB pod, and the pod needs to create the DB container. But first, Kubernetes must download the mysql:5.7 image from Docker Hub, and that can take a few minutes.

In other words, you'll need to be patient the first time you run these commands until the mysql:5.7 image is cached by your cluster.

The Nextcloud Server Deployment

Now that the database is up and running, let's create the Kubernetes config files for the Nextcloud server components.

nextcloud-server-1

Here we have the Kubernetes deployment named nextcloud-server As you can see from the replicas field, this deployment makes sure that exactly one server pod is always running.

nextcloud-server-1a

Notice that the container's image field still needs to be filled in. Let’s fix that. I'm going to google “Docker Hub Nextcloud” to find the Nextcloud Docker Hub page.

docker-hub-1

The Nextcloud documentation says, “the apache tag contains a full Nextcloud installation including an apache web server.” That's what we're looking for, so let's go with the nextcloud:16-apache Docker image.

nextcloud-server-1b

While Nextcloud does use our MySQL database to store stuff, some things are not stored in the DB--things like user-uploaded files and whatnot. Those files get saved in /var/www/html. We need to mount a volume onto that directory to make sure files get persisted in case the container dies and gets recreated. Here’s the code to do all that:

nextcloud-server-1c

Note that we are using the exact same volume backed by the same PVC as the MySQL database. This was just a design decision that I made to keep things simple.

The Service for the Server Deployment

We also need to create a service for our nextcloud-server pod.

nextcloud-server-1d

Clients who want to talk to the server should instead communicate with the service. All packets that the service receives on port 80 will get forwarded to port 80 on a nextcloud-server pod.

Create the Nextcloud Server

In order to create the Nextcloud server deployment and service, simply run the following command in the same directory as before:

kubectl apply -f nextcloud-server.yaml

Kubernetes Ingress

You might be wondering why we haven't defined any database-related environment variables.

Well, we are going to configure that stuff on the Nextcloud web page. What we need to do now is make the Nextcloud server browser-accessible.

By default, clients outside of the cluster cannot communicate with pods inside of the cluster. The way that we are going to solve this is by creating a Kubernetes Ingress object. It will allow external clients to connect with the Nextcloud server in our cluster. (It can do much more, but don’t worry about that right now.)

Okay. Here is our Ingress config file.

cluster-ingress-1-1

Let's assume that files.mysite.test resolves to the IP address of our Minikube node--i.e. the one (virtual) machine in our development cluster. Then all HTTP requests addressed to files.mysite.test/ will get routed to port 80 on the service named nextcloud-server.

cluster-ingress-1a-1

Enable the Minikube Ingress Addon

Before we create the ingress object, we need to enable ingress on our cluster. You see, ingress is not enabled on Minikube by default.

term-2

Enable it using this command:minikube addons enable ingress

term-3

Create the Ingress Object

In order to create the ingress object, we need to go to the directory containing the config file.

term-4

Then run the following command:

kubectl apply -f cluster-ingress.yaml

Update the "hosts" File

If we were to type files.mysite.test/ into my browser, we’d get a 404 error. The request would not get routed to the Nextcloud server because that host name doesn't yet map to the IP address of our Minikube node.

Let's get the IP address of Minikube:

term-5

Then for test purposes only, I'm going to add the following entry into my computer's hosts file.

term-6

Now we can visit the Nextcloud site in the browser!

Configure and Try Out Nextcloud

We are ready to visit the Nextcloud site and test things out. So let's visit http://files.mysite.test in the browser.

nextcloud-site-1

We need to configure the site. Start by adding credentials for Nextcloud. I'm going to use admin/admin for simplicity.

The next step is to configure the server to use the MySQL database that we created for this project. So click on:

  1. Storage & database
  2. MySQL/MariaDB

nextcloud-site-2-1

  1. Fill in the database user and password with the values from the nextcloud-db-secret (see it).
  2. Fill in the database name with the value from the MYSQL_DATABASE environment variable in the MySQL deployment config document (see it).
  3. Change localhost to the domain and port of the DB service. This ends up being <SERVICE NAME>:<SERVICE PORT> (see it).
  4. Click Finish setup.

nextcloud-site-3

The setup is complete, and you should see something like this:

nextcloud-site-4

I'm glad you made it all the way to the end! You now have you own locally-hosted Nextcloud server and a better idea of what working with Kubernetes looks like.