Set up Nextcloud on GCP, a walk through

Nextcloud is a self-hosted platform that can replace all of your Dropbox usage. It also has a bunch of apps that enhance it into a replacement for Google Drive, Trello, and more.

We’ll walk through setting up Nextcloud from a container, starting another container to use Let’s Encrypt to get an SSL certificate, and finally set up an nginx reverse proxy to enable HTTPS on the insecure Nextcloud setup.

Table of contents

Problem statement

I want to be able to sync data between different computers. Currently, I use a USB key. It works, it’s usually OK. But last time I forgot the key in my desktop and couldn’t work, so that kinda sucked.

I also want to run the Collabora app (for Google Drive replacement), and I want to pay as little as possible for this because I’m just testing it out.

There a loooong Gist on how this can be set up, but I’m pretty confident that it’s a massive overkill.

Why GCP?

Well, Google Advanced Protection secures my account very well. You can also configure backups decently quickly. The only problem is that it’s kind of expensive compared to Linode.

But, I already have a GCP account to host websites, so it’s worth investigating that option.

Walk through

First, we’ll try the Google Cloud Engine such that it runs docker containers. A decent amount of configuration will be needed. Note that I will set this up on a subdomain of nyxspace.com, and that site already has an HTTPS certificate. I’ll follow the official README, so hopefully all of this works well…

Choosing the setup

  1. I’ll opt for Cloud Compute Engine: this means the server is dormant most of the time (I’m the only user) and will boot up when requests are made. That should bring down the cost to $0/month for that container.
  2. I’ll use the Cloud SQL because I want to make sure there are backups.
  3. I’ll use an external persistent storage to enable snapshots.

Pulling the Nextcloud image

To setup cloud run, the image needs to be available on your GCP account.

To do so, follow these steps from stack overflow.

  1. Open a Cloud Console (top right)
  2. docker pull nextcloud:21
  3. docker tag nextcloud:21 gcr.io/[PROJECT-NAME]/nextcloud:21
  4. docker push gcr.io/[PROJECT-NAME]/nextcloud:21

Set up Cloud SQL

You can skip this step if you want to use the embedded SQLite.

  1. In the apps, select “Cloud SQL” and “create instance.”
  2. Select MySQL. Nextcloud works best on that according to their docs.
  3. Make sure to enable the Cloud Engine API (GCP will request that and will prompt you to do it if it isn’t enabled yet).
  4. Choose MySql 8.0+, that’s a Nextcloud requirement.
  5. This server is for personal use, so I’m not making it High Availability. That’s an overkill for me.
  6. I’m also customizing the instance to be cheap: shared code, 1 vCPU, 0.67 GB of memory. That means it’s limited to 125 MB/s and that should be plenty.
  7. Then, I’m starting with an HDD storage and just 10 GB of capacity. I’m configuring this instance to scale when the threshold is reached. Note that this does store the Nextcloud data, so it’s the most important thing!
  8. Point in Time recovery is probably useful for a production server, not for a personal server, so screw that.

Configure the SQL database

  1. Click on “Connect to this instance.”
  2. Enter the password you generated for this database.
  3. Create a database: CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

Allow connection to the SQL database

We need to reserve an external IP address for the VM just so it can talk to the SQL server. I guess we need that anyway to make sure that restarting the container won’t cause us to reconfigure the DNS, but it’s still extra cost and a notch annoying.

  1. Select the VPC Network application in the left side bar.
  2. If the VM is already running, find it in the list and change the “Type” from Ephemeral to Static. If the VM is not running, then click “Reserve Static Address” at the top of the page.
  3. Then, return to SQL, click on the name of the instance and then the “Connections” tab on the left.
  4. Click “Add network” and enter the static IP. Give that connection a name.
  5. Finally, copy the IP of the SQL instance (not the static IP). We’ll need to add that to the environment variables of the Nextcloud docker deployment.

Set up the Cloud Compute Engine

Select the Compute application, and then “create instance.”

Since I’ll be using an external SQL, I think I can get away with an e2-micro. It’s only $7.12/month in the Iowa region.

Machine configuration

  1. e2-micro, 1 vCPU 1 GB of RAM
  2. Select “deploy container” and enter the gcr.io tag you selected earlier.
  3. Then, we need to set up the MYSQL environment variables. Otherwise, Nextcloud will use SQLite and we don’t want that for sure.
  4. In the “environment variables” section, create envvars for MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD and MYSQL_DATABASE. These should match what you’ve entered initially.

Volume mount

  1. Click “Add volume”
  2. In the volume type, select “disk.”
  3. According to the docs, all persistent configuration is in /var/www/html, so that’s the mount point we’ll use.
  4. Click “attach disk”. That will bring you to the bottom of the page.
  5. Click “Add new disk”. Enter an appropriate name for that permanent disk (e.g. nextcloud-data). I’ve selected “Balanced persistent disk” because I want to save money.
  6. We’ll set up the snapshot for this disk later, so keep the snapshot schedule to “no schedule.”
  7. Note that it’s $1/GB/month. Since this is only some config stuff, I’ll choose 20 GB and am crossing my fingers that this is enough.
  8. For better security, select “customer supplied key” for the encryption and provide a valid encryption key.
  9. Click “Done” on the new disk wizard, return to the “edit volume mount” and select the newly created disk.
  10. Make sure to set this disk as “read/write.”

Security config

In the “Security” tab, make sure to paste a valid SSH public key so you can SSH into this instance.

Make sure to select “Allow HTTP” and “Allow HTTPS” communication. Don’t worry, we’ll block HTTP in the Nextcloud config (I think we can figure that out).

Management config

  1. Check the box for “delete prevention” to make sure it can’t be deleted by accident.

Cost!

So far, this instance is costing $9.12/month. Sure, Dropbox is at $12/month for 2 TB of data, but they’ll be snooping around.

Configuration of the instance

Once it’s booted, the persistent storage that we connected to /var/www/html won’t work because it isn’t formatted… well actually it seems like it’s formatter automatically.

  1. Navigate to http://34.XYZ.XYZ.65/ and you should see the Nextcloud login! If the MYSQL server is not properly set up, you’ll see a warning that you’re using SQLite. If so, edit the envvars of the instance, and restart it. It should keep the same IP…

To test that everything seems fine, you can debug things with this:

  1. In the instance list, select the one you just created and click “SSH”. If that doesn’t seem to work (it didn’t for me), use you favorite SSH client (like ssh).
  2. Run docker container list to grab the ID of the container that is running Nextcloud.
  3. Then, check the logs: docker logs CONTAINER_ID. You should see that you access this instance! YAY!
  4. Have a look in /mnt/disks/gce-containers-mounts/gce-persistent-disks/PERSISTENT_DISK_NAME/. You’ll see a bunch of useful files. Let’s figure out what those do.

Useful apps

  1. Once you’ve created the admin account, click on the top right circle and then + Apps.
  2. Browse the feature list. There, you should consider installing the Yubikey app, the OTP app, End to End encryption, and whatever else you fancy
  3. Then, in the “Settings,” make sure to enable end to end encryption.

Enable HTTPS on the VM

This was a bit annoying. I followed this StackOverflow answer: https://stackoverflow.com/questions/62493334/ssl-certificate-on-container-optimized-os-docker .

  1. Create a service account. The “ID” does not actually matter, I chose dns-admin.
  2. When the service account is created, go into its details and create a “key” in JSON format (that’s the recommendation anyway).
  3. Download that.
  4. SSH into your instance and create the relevant file: nano dns-svc-acct.json
  5. Then, start the docker certbot image.

$ docker run --rm -v /etc/letsencrypt:/etc/letsencrypt:rw -v ${PWD}/dns-svc-acct.json:/var/dns-svc-acct.json certbot/dns-google certonly --dns-google --dns-google-credentials /var/dns-svc-acct.json --dns-google-propagation-seconds 90 --agree-tos -m EMAIL@DOMAIN.TLD --non-interactive -d SUB.DOMAIN.TLD

Note: If your domain is registered using Google Domains, you’ll need to change the name servers to those from Cloud DNS. It isn’t too complicated, but it’s an extra step. Source: https://community.letsencrypt.org/t/problem-with-dns-01-challenge-using-dns-google-authenticator/54583/6 .

  1. Create a public DNS zone: https://cloud.google.com/dns/docs/zones#creating_managed_zones . (This allows you to subsequently create non-public zones.)
  2. I entered my highest level domain for this.
  3. Copy all of the name server entries to the DNS configuration. Note that the Google UI team decided to add a . at the end of each… but that’s an invalid NS so you gotta remove that. Arguably bad UX decision.
  4. Your previous domains records should still work and are still configured in Google Domains

If certbot works, the output should be something like this:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator dns-google, Installer None
Requesting a certificate for SUB.DOMAIN.TLD
Performing the following challenges:
dns-01 challenge for SUB.DOMAIN.TLD
Unsafe permissions on credentials configuration file: /var/dns-svc-acct.json
Attempting refresh to obtain initial access_token
Refreshing access_token
Waiting 90 seconds for DNS changes to propagate
Waiting for verification...
Cleaning up challenges
Attempting refresh to obtain initial access_token
Refreshing access_token
IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/SUB.DOMAIN.TLD/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/SUB.DOMAIN.TLD/privkey.pem
   Your certificate will expire on 2021-08-06. To obtain a new or
   tweaked version of this certificate in the future, simply run
   certbot again. To non-interactively renew *all* of your
   certificates, run "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

Note: This step probably needs to be redone if the server is restarted. Yay.

Enable HTTPS on Nextcloud

This is a bit annoying too. Turns out that the image I chose above does not support HTTPS at all. Yay.

So we’re going to setup NGINX as a reverse HTTPS proxy. A lot of the tutorial install stuff directly on the machine. But we’re in 2021, so we’re going to set this up as a container.

We’ll broadly follow this tutorial: https://phoenixnap.com/kb/docker-nginx-reverse-proxy .