Deploying Nextcloud locally with LXD
As I mentioned in the previous blog post, I am now in the process of moving some of my homelab services from Docker to LXC (Linux Containers). Both of them are virtualization technologies and are based on similar concepts, but recently I found LXC more tempting.
Disclaimer 1: I would not call myself an expert in either Docker or LXD, what I write below is based on the limited knowledge that I have on those topics, and there is a high possibility I might be wrong in some places. Let me know if you spot any errors.Disclaimer 2: Everything described here I have first created and configured for my own needs, and only later decided to write up in a blog post. I am using NextcloudPi in my homelab as an actual “production” of storing documents and other data. So this is not a tutorial for the sake of writing a tutorial, it’s a description of my real life usage.
I’ve been using Docker for years now, both at work and in my homelab, and I find it an excellent tool. Then again, I believe its usecase is mostly for small, ephemeral containers that run a single process, like a webserver or a video converter. LXC containers (I know, I know, PIN number) on the other hand are closer to virtual machines, holding state and having multiple processes running in them. LXC also provides tooling for doing snapshots and backups of running containers. And when everything like the database and the server is running in the same container, it’s easier to backup and move it, in contrast to running a whole group of containers with docker compose
.
Because of that I feel that LXC containers are well suited to run some of the long-lasting, stateful homelab services.
Another thing that led me to LXC is me wanting to have my own Nextcloud instance, and this is the main topic of this post.
LXC vs LXD
The topic of “LXD vs LXC” is confusing to say the least. The way I understand it is that LXD is something like a frontend, allowing for easier management of LXC. LXC is the underlying framework that runs the containers, while LXD allow for administration and orchestration of the LXC containers. Something like Docker Desktop for managing Docker containers which are being run under the hood by the Docker Engine. Also LXD being created by Canonical is Ubuntu-centred while LXC is distro-agnostic. Finally, it is possible to just use LXC without the added benefits of LXD.
Nextcloud
Nextcloud advertises itself as a “fully open-source, on premise content collaboration platform”. What it actually means for us, homelabbers, is that it provides a FOSS alternative to commercial cloud services like the Google suite of apps like Calendar and Drive. With Nextcloud we can have those services running privately in our homelabs, without subscription feeds, or snooping through our files. Running your own instance fits very well into the topic of degoogling, as it allows to move away from large corporations and take your data into your own hands. There’s much more it of course, but this blog post is more on the technical site of setting up your own instance.
NextcloudPi
When looking for a way to deploy Nextcloud to my private homelab, I came across a subproject of NC, called NextcloudPi. As the name suggest, one of the main goals of NCP is to provide a way to run Nextcloud on a Raspberry Pi. The creators provide an image that can be flashed to an SD card to run on a Pi. I don’t want to run NC on a Pi, when I have an x86 server sitting under my desk, so I chose another solution that they provide: an LXD container image.
Setting up LXD
I think it’s a good idea, if you are using LXD for the first time, to install and try it out first on your local PC/laptop, and later, once you went through the whole process, install it in a headless homelab. It will make some things easier to test.
There are multiple ways to install LXD, I went with the simplest/laziest option that is installing it from snap. My laptop is running Kubuntu, and my homelab’s OS is Debian with added snaps (I know, blasphemy), so snaps are fine with me. Here are the installation docs.
When installing from snap, it’s as simple as (needs to be run as root)
snap install lxd
lxd init
If you are running LXD on machines that have a single drive, or you want to have a basic setup for testing, choose the storage backend to dir
. If you have the possibility, choose ZFS and provide a separate dataset for storage.
Once LXD is running, it’s time set up the LXD Web UI. There are multiple way to administrate LXD, but the simplest way is to use the browser. If you are running it on your computer, just open localhost at port 8443. If you installed it on another server, open it’s IP address with the port 8443. A first-use wizard will guide you through the initial setup, and will show how to generate a certificate. It is a bit more complicated than setting up a username and password, but should not cause any problems.
Before moving to the next part, have some fun with LXD, try to run a container, open a terminal inside it, try to update the packages, stuff like that. Just to get a feel of what is happening.
BTW, the terminology that LXD is using for a running container is “instance”, and this is the word I will be using from now on.
And if you created an instance, and it cannot access the internet, check this forum thread.
Running NCP
Now it’s time to run NextcloudPi. Download the NextcloudPi image from the GitHub downloads page, select the file with LXD in the filename and the architecture of your computer (most likely x86). Download it, but do not unpack.
In the LXD Web UI go to Images -> Upload image, select the downloaded file, add a nice alias for the name if you want. Then Instance -> Create Instance -> Browse Images and at the top of the list there should be the newly uploaded NextcloudPi one. For now there is no need to modify other settings, just select Create and start.
Form to upload a container image into LXD
A list of all available containers images LXD, with the NextcloudPi’s one on top
Accessing NextcloudPi
NextcloudPi is now running on your computer as an LXD instance, but how to access it? It depends on where you are running your LXD service:
Running on the same PC
Accessing the NextcloudPi instance in LXD running on the same computer that you are using is the simplest way. By the power of DNS magic, NCP should be reachable at https://nextcloudpi.local
. If that is not the case, the second option is to open in the browser the instance IP address, which can be found in the instance overview tab in the LXD web UI.
Instance summary page, with the highlighted IPv4 address of the instance
Running in the LAN
This is where things get a bit more complicated. For an overview of methods how to reach an LXD container running on a machine somewhere in your LAN, I recommend this great video done by one the LXD developers: Accessing services running in LXD instances. From all the methods presented there, I have been using two:
Configuring specific routing in the router
The first method I used is setting up routing in the router. I have a MikroTik router that allows for almost infinite configuration, so that was not a problem, but I’m not sure how feasible it is in typical home routers. Also configuring it differs between router makers, so I won’t be giving exact instruction, just an example:
For my MikroTik router it was a matter of opening a terminal window in the router web UI, and setting the routing:
[admin@MikroTik] > /ip route add dst-address=10.54.60.0/24 gateway=192.168.10.240
The dst-address
is the subnet of the LXD network, to find it take the IP address of you instance, and replace the last number group with 0.24
. For example, if you instance IP is 10.54.60.123
, the subnet is 10.54.60.0/24
. The gateway
is the IP of the host machine that is running LXD. Thanks to that command, your router will know that if you want to reach the IP address of your NCP instance, for example 10.54.60.123
, the request has to go first to 192.168.10.240
, and that machine will route it further to 10.54.60.123
.
The upside of this approach is that it only needs to be done once, and will handle any containers run in LXD. The downside is that not all routers support this.
Also with this approach, http://nextcloudpi.local/
will not work out of the box. To make it work, you’ll need to manually set up a DNS entry, either in the DNS server, if you have your own, or in the /etc/hosts
file.
For more information about setting up local domains with DNS, check out my How to set up subdomains in the homelab with PiHole and Caddy blog post.
My personal problem with this approach is that while it works, it is extremely slow, I need to wait at least 5-10 seconds for the webpage to load. I am suspecting this is caused by my non-standard network solution, I have a WiFi bridge made from two Mikrotik Audience Access Points, and have the routing configured on one of them, the one closer to the ISP router. This is something I will need to investigate in the future. For you it will most probably work fine.
Proxy devices
I won’t lie, this is the part of the LXD config that I understand the least, but I found how to use it to make it work the way I want it to. If you know how to do it better, more properly, let me know.
Proxy devices allow to configure a connection between the host and the instance, so that accessing the host on a specific port takes the request to a specific port on the instance to which the proxy is attached to. I have two proxy devices configured, first one forwards the connection to the host port 9290 to the instance port 443 (default for HTTPS) and the second device forwards from host 4443 to the instance 4443 for the management site.
This way, when I go to HOST_IP:9290 I am taken to the Nextcloud main site, and when I go to HOST_IP:4443, I go the admin portal.
I am using port 9290 on the host because I also have other services running, and so ports 80 and 443 are already occupied.
I also found out that the admin portal will not work if you change its port in the proxy device, it expects to be accessed on port 4443, otherwise it will not allow to change any settings.
The Bind and NAT mode options I leave undefined, from my limited experience I did not see difference when I changed them to different options. The Listen IP address is 0.0.0.0 (any address) and the connect one is 127.0.0.1 (localhost of the instance). For me it kinda works. How correct it is? I am not sure. I would love to hear your feedback.
Accessing from anywhere
You can now access the instance locally. But what if we want to access our Nextcloud from any place in the world, for example to upload invoices or photos from holidays? For that I am using Tailscale, a zero config VPN. I’m a big fan of them, I have them on all my devices, even my mobile phone. No, they do not pay me, I just like their services.
To install Tailscale I just use their install script, which I launch in the instance terminal. The terminal can be accessed on the instance page in the LXD Web UI. Once Tailscale is installed and authenticated, you will get an IP address and a domain to access it from anywhere.
Note that in order for the Tailscale domain to work, you will need to add it to the “trusted domains” setting in the NCP admin portal (the one on the :4443 port) or by editing the /var/www/nextcloud/config/config.php
in the instance terminal window.
NextcloudPi initial config
Once the container is running, and you can access it, the time has come to do the initial setup. NextcloudPi built-in HTTP server works on the standard ports 80/443, so unless you changed the port in the proxy device setup, pasting only the domain or the IP address in the browser will work.
You will get an error that the connection is not secure, that is not a threat, it’s just the way NCP runs by default.
NCP has HTTPS implemented out of the box, but it is using a self-signed TLS certificate, and the browsers do not like it. Just confirm that you accept the risk and want to continue. If you know how to solve it for locally running services using HTTPS, let me know.
Once the warning page is gone, you will be greeted with the NCP welcome page with two sets of usernames and passwords. Note them before clicking “Activate”! Then again, if you continued before saving them, that is not a problem, just stop and remove the instance and create a new one. The beauty of virtualization is that killing and recreating virtual machines is super simple and fast.
The first login and password is for the server administration page at port :4443. This page is for all of the low-level configuration and administration tasks to which regular users should not have access, for example the database settings or software updates.
The second set of credentials is for the first user of your NCP instance. That user will also have the administrator permissions, allowing them to do some additional configuration for all users of the instance, such as the quotas, languages and themes
Once you have written down both sets of credentials, click Activate, and you will be taken to the main page of your Nextcloud account. You are an administrator, so you can start with creating more users in the Administrator settings.
And that’s basically it, you now have a running Nextcloud instance to which you can upload files, create notes, make appointments in the calendar etc.
Backups
Now that you have all your precious and important data in NCP, it would good to back it up. The available backup solutions for NCP are snapshots and exports, and both serve slightly different purposes.
Snapshots
Snapshots allow to freeze the instance state in a certain moment in time, like taking a photo. If you, for example, remove from your NCP drive a file that you did not want to remove, you can restore the whole instance from a snapshot that was taken before you removed that file. You will lose all the changes that you did in the time between now and the creation time of that snapshot, but your precious file will be restored.
List of snapshots created for the NextcloudPi instance
LXD allows to configure the automatic creation of snapshots on schedule in the Snapshot tab of the instance settings. There you can also list your snapshots and select the one to restore from. A good idea is to set the expiration date of the snapshots so that they do not take too much storage space.
Exports
Snapshots are great and all that, but they have one significant problem: they are stored on the same machine as the LXD service. If that machine breaks, you lose the running instance and all of its snapshots.
The solution for that is to export the instance to a file, which can then be moved to different machine(s). For that, we need to go to the terminal on the host where the LXD service is running. To create an archive, this is the command:
lxc export nextcloudpi --instance-only "ncp-$(date '+%Y-%m-%d').tar.gz"
Running this will create a tar.gz archive with today’s date in the filename. The --instance-only
flag defines that only instance should be archived without the snapshots. If you want to also export the snapshots, remove the flag but bear in mind that the file will be massive. To automate this, create a bash script that will generate the archive and rsync it to some other place. Add it to cron to run it periodically.
#!/bin/bash
DATE=$(date +%Y-%m-%d)
FILENAME="ncp-$DATE.tar.gz"
lxc export nextcloudpi --instance-only ncp-$DATE.tar.gz
rsync -azP $FILENAME 192.168.1.2:/home/stefan
Change the IP address and local path to what you have.
To import the resulting archive back to LXD:
lxc import ncp-2025-04-12.tar.gz
This command will import the exported instance. It will be added to the list of instances in the LXD Web UI, in the stopped state, and will need to be started to use it.
Bottom Line
So now you have a local NextcloudPi instance which you can use to organize your life and files. Of course this post only scratched the surface of what LXD and Nextcloud can do, but I’m hoping it give a good basis to develop upon. As I use Nextcloud more, and move more of my services to LXD, I will be writing more articles on the topic. In the meantime, thanks for reading!
If you enjoyed this post, please consider helping me make new projects by supporting me on the following crowdfunding sites: