Overview
The following goes over nextcloud setup in declarative nixos-container with PSQL. I’ve set requirements which includes:
- containerize nextcloud
- declarative container as nixos module
- access
$ip:$port
for nextcloud from host - mount nextcloud data dir
- psql instead of sqlite within same container for nextcloud DB
- data backups possible for data dir and psql
Outline:
Dev Log
There were several learning experiences for myself, but I’ll focus on the more tricky ones with fewer solutions online.
Debugging each issue was a slow process, especially with changes to the container config as
I’m doing a nixos-rebuild switch
. It can take up to 2 minutes to rebuild my system with changes.
Networking and containers.nextcloud
The wiki page for nixos-containers uses nextcloud as an example, yet it’s ambiguous, misleading and incomplete in describing
setting up the containers own network stack and exposing bound $ip:$port
to host. This guy actually gets us going
in the right direction https://blog.beardhatcode.be/2020/12/Declarative-Nixos-Containers.html.
We define our nextcloud container as
containers.nextcloud = {
autoStart = true;
privateNetwork = true;
hostAddress = "192.168.100.2";
localAddress = "192.168.100.11";
};
Find our host networking interface with ifconfig
. Oftentimes is eth0, or enp something, then set
networking.nat = {
enable = true;
internalInterfaces = ["ve-+"];
externalInterface = "enp42s0";
};
nextcloud will bind to all within the container on port 80 for ipv4 and ipv6 from the looks of it
[root@nextcloud:~]# cat /etc/hosts
127.0.0.1 localhost
::1 localhost
127.0.0.2 nextcloud
::1 nextcloud
[root@nextcloud:~]# netstat -tuln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:5432 0.0.0.0:* LISTEN
tcp6 0 0 :::80 :::* LISTEN
tcp6 0 0 ::1:5432 :::* LISTEN
We can infer process binding to the ips on port 5432 is psql since 5432 is its default port.
There’s several small configs we should be setting within the container config that includes:
services.nextcloud.hostName
services.nextcloud.extraOptions.extraTrustedDomains
Open port 80 within the container (tcp and tcp6)
networking = {
firewall = {
enable = true;
allowedTCPPorts = [ 80 ];
};
};
Though we’ll only be using ipv4 ip from the container.
Admin User
I use git-crypt
in my nixcfg/ (system flake.nix) for secret file management.
So within the container, I link to it in the nix store with
systemd.tmpfiles.rules = [
"L+ /var/lib/nextcloud/admin-pw 0440 nextcloud nextcloud - ${adminPwFile}"
];
where adminPwFile
is declared derivation to a direct file within my flake.nix directory in a git-crypt
encrypted
directory commited in git
.
It’s generally recommended to use sops-nix
or agenix
, and to not store plaintext secrets in the nix store. My management of
secrets here is simplified as sops-nix
won’t let you commit multiple secret files. Leveraging 1 file
decrypted at development and build time is very conveinent, e.g.:
let
adminPwFile = ../.secrets/elk/nextcloud-admin-pw;
in {}
By now, we should be able to access http://192.168.100.11 from the host. We can verify the ip of the nextcloud container
with nixos-container show-ip nextcloud
PSQL
Not using the default sqlite is encouraged by nextcloud as per their warning when visiting the default landing page, /login
Performance warning You chose SQLite as database. SQLite should only be used for minimal and development instances. For production we recommend a different database backend. If you use clients for file syncing, the use of SQLite is highly discouraged.
If you’re having troubles migrating to PSQL, reference my config below and visit the Debugging section.
We:
- enable
postgresql
service - ensure nextcloud DB and user
- add nextcloud user to postgres group
- set nextcloud config to use pgsql
- enable
postgresqlBackups
Things are setup, but of we need to test and verify we have a plan and the backup + restore process works :)
Bind Mount between Host and Container
I bind the data dir in container, /var/lib/nextcloud/data
to host /mnt/nextcloud-data
with read + write permissions.
I use systemd.tmpfiles.rules
to create the directory on the host with the appropriate file privileges and user:group.
One improvement I made to my system flake.nix along the way is explicitly setting my uid and gid of my personal user to 1000:100 for consistency. I had some weirdness going on with nixos where using
systemd.tmpfiles.rules = [
"d /mnt/nextcloud-data 0755 j users"
];
would set 999:999 but then my uid and gid would change and some other /mnt/nextcloud-data
without sudo
.
I then moved my uid and gid to variables passed along to all modules for access as I do with user
and primaryGroup
to then be able to do this more declaratively and variabely
systemd.tmpfiles.rules = [
"d /mnt/nextcloud-data 0755 ${toString vars.uid} ${toString vars.gid}"
];
defining vars
as a specialArgs
at my top-level nixosConfigurations.<host>
Debugging
There were quite a few errors I was running into. nextcloud and the nixos config for it isn’t the best experience especially when switching things around.
As long as we nixos rebuild switch successfully from the host system, we can
sudo nixos-container root-login nextcloud
to then further inspect configs, services, etc. Most of my debugging takes place within the container.
Early on testing connectivity from host, I noticed within the container, I could get a response from
curl http://nextcloud
but not localhost
(reference my previous /etc/hosts
within container). I would later set my container local ip
to the hostName
value to supplement this.
Our config dir looks like this
[root@nextcloud:~]# l /var/lib/nextcloud/config/
total 16K
drwxr-x--- 2 nextcloud nextcloud 4.0K Sep 7 14:40 .
drwxr-x--- 5 nextcloud nextcloud 4.0K Sep 7 13:53 ..
-r-------- 1 nextcloud nextcloud 0 Sep 7 14:40 CAN_INSTALL
-rw-r----- 1 nextcloud nextcloud 1.3K Sep 7 14:27 config.php
lrwxrwxrwx 1 root root 64 Sep 7 13:53 override.config.php -> /nix/store/aa5v311q7c54qibr0nsfzm4xwvr4v237-nextcloud-config.php
The single most common error with setting up nextcloud will be thrown in
[root@nextcloud:~]# systemctl status nextcloud-setup.service
× nextcloud-setup.service
Loaded: loaded (/etc/systemd/system/nextcloud-setup.service; enabled; preset: enabled)
Active: failed (Result: exit-code) since Sat 2024-09-07 14:10:08 PDT; 10min ago
Process: 285 ExecStart=/nix/store/4iyi845z4n2rl3frrih8a0f0g16cjn0h-unit-script-nextcloud-setup-start/bin/nextcloud-setup-start (code=exit>
Main PID: 285 (code=exited, status=1/FAILURE)
CPU: 106ms
Sep 07 14:10:08 nextcloud systemd[1]: Starting nextcloud-setup.service...
Sep 07 14:10:08 nextcloud nextcloud-setup-start[293]: Nextcloud is not installed - only a limited number of commands are available
Sep 07 14:10:08 nextcloud nextcloud-setup-start[293]:
Sep 07 14:10:08 nextcloud nextcloud-setup-start[293]: Command "upgrade" is not defined.
Sep 07 14:10:08 nextcloud nextcloud-setup-start[293]:
Sep 07 14:10:08 nextcloud systemd[1]: nextcloud-setup.service: Main process exited, code=exited, status=1/FAILURE
Sep 07 14:10:08 nextcloud systemd[1]: nextcloud-setup.service: Failed with result 'exit-code'.
Sep 07 14:10:08 nextcloud systemd[1]: Failed to start nextcloud-setup.service.
I found many threads in repos issues and forums on the matters, but none go into detail or provided solutions that worked for me at the time of writing, so I write an entire post about it. Unfortunately, we’re facing the anti-pattern of 1 vague error describing a wide variety of issues as seen throughout nextcloud issue threads.
One script we reference is that of the nextcloud-setup.service
ExecStart script, i.e.:
[root@nextcloud:~]# cat /nix/store/4iyi845z4n2rl3frrih8a0f0g16cjn0h-unit-script-nextcloud-setup-start/bin/nextcloud-setup-start
<output omitted>
in here, we can see the validation conditions, setup process, and determine what to run to debug points of failures
At times, my /var/lib/nextcloud/config/config.php
was empty (0 bytes).
I would just delete it then try restarting the nextcloud-setup.service
or running the command directly myself,
that is,
mv /var/lib/nextcloud/config/config.php temp
sudo -u nextcloud DBPASS="" ADMINPASS="$(<"/var/lib/nextcloud/admin-pw")" \
/nix/store/yfrk6xybwmaxxdblks0pj4a473j0z819-nextcloud-occ/bin/nextcloud-occ maintenance:install \
--admin-pass "$ADMINPASS" \
--admin-user "admin" \
--data-dir "/var/lib/nextcloud/data" \
--database "pgsql" \
--database-host "/run/postgresql" \
--database-name "nextcloud" \
--database-pass "$DBPASS" \
--database-user "nextcloud"
it’s possible that you get this error from the nextcloud-occ
command,
The Login is already being used
in which case, you may be facing an issue with the state of nextcloud DB used, in my case, psql.
If you migrated from sqlite (default DB) to psql (and did your data backups), you may need to
sudo -u postgres psql -c "DROP DATABASE nextcloud;"
sudo -u postgres psql -c "CREATE DATABASE nextcloud OWNER nextcloud;"
then try restarting the service or running the nextcloud-occ
command above again.
Along the way, we may the error from the nextcloud webpage
Error It looks like you are trying to reinstall your Nextcloud. However the file CAN_INSTALL is missing from your config directory. Please create the file CAN_INSTALL in your config folder to continue.
Within the container we then additionally set a 2nd rule,
systemd.tmpfiles.rules = [
"L+ /var/lib/nextcloud/admin-pw 0440 nextcloud nextcloud - ${adminPwFile}"
"f /var/lib/nextcloud/config/CAN_INSTALL 0400 nextcloud nextcloud -"
];
Alternatively, you could try to touch
the file then restart the service as needed.
The implications of CAN_INSTALL
and its impact is up to you to research as I’m just hacking my way through for my own
initial requirements without risking any real data. I’ll later remove the 2nd rule for production release.
Production Deployment
My requirements are simple. I’m the only one using this. I’m putting this on a public facing endpoint, but have multiple layers of security, that is:
- allowlist ip(s) for access (access control)
- username and password per user (access control)
- TLS encrypting HTTP (encryption)
In the future, I could utilize nextcloud’s server-side or end-to-end encryption, encrypt the directory or mount an
encrypted drive for binding host /mnt/nextcloud-data
to container nextcloud data. I could consider rolling my
own certificate authority for the TLS.
Allowlist my public IP for controlling access with
extraConfig = ''
allow ${permittedIp};
deny all;
'';
I add an A record in my zone file for my subdomain, mysubdomain.example.com
, to submit to my domain defined in AWS Route 53.
Enable TLS for my subdomain with
virtualHosts."mysubdomain.example.com" = {
enableACME = true; # automated cert management env
forceSSL = true; # use https, not http
# ...
};
There’s several other configs for setting up TLS, Let’s Encrypt, and nginx for my domain, but I won’t go into detail here.
Reverse proxy requests to mysubdomain.example.com
to 192.168.100.11
on the host with
locations."/" = { # reverse proxy requests to this location on the host
proxyPass = "http://192.168.100.11:80";
# ...
};
One improvement I could make abstracting this magic value, 192.168.100.11
, to a variable.
But wait, there’s more, errors.
Performance warning You chose SQLite as database. SQLite should only be used for minimal and development instances. For production we recommend a different database backend. If you use clients for file syncing, the use of SQLite is highly discouraged.
I’d infer this implies psql is misconfigured.
● nextcloud-cron.service loaded failed failed nextcloud-cron.service
● nextcloud-setup.service loaded failed failed nextcloud-setup.service
● nextcloud-update-db.service loaded failed failed nextcloud-update-db.service
3 services failed while all else is fine
nextcloud-setup.service
logs show the stacktrace ending inocc(11): require_once('/nix/store/l2d7...')
which isn’t useful.nextcloud-update-db.service
logs show the most common catch-all error,
Nextcloud is not installed - only a limited number of commands are available
Fixed with
rm /var/lib/nextcloud/config/config.php && systemctl restart nextcloud-setup.service
Well, nextcloud-setup.service
is still failing with the same error, but I’ll check on that later as something additional broke.
I can curl http://nextcloud
in container, but I can’t curl http://192.168.100.11
on host.
Thinking back to it, I forgot to add crucial config for networking on my elk
host to enable the
network addressing translation config. That is, I needed to add
networking.interfaces.eht0.name = "ens18";
Unfortunately I don’t have a good solution for making this declarative.
I’m still facing the same problem, let’s see what does work.
[j@elk:~]$ nix-shell -p traceroute
[nix-shell:~]$ sudo traceroute -T -p 80 192.168.100.11
traceroute to 192.168.100.11 (192.168.100.11), 30 hops max, 60 byte packets
1 nextcloud.containers (192.168.100.11) 0.358 ms 0.057 ms 0.046 ms
TCP works, but not HTTP.
Double checking HTTP response within the container, with curl --verbose http://nextcloud
, I find it’s actually returning
a 400 and a html response body that has the error message
Access through untrusted domain
Please contact your administrator. If you are an administrator, edit the “trusted_domains” setting in config/config.php like the example in config.sample.php.
I add my subdomain serving nextcloud and the IP assigned to the host for the primary network interface to the trusted_domains
list.
services.nextcloud.settings = {
trusted_domains = [ "192.168.100.11" "mysubdomain.example.com" my-ip ];
};
I forgot a semicolon in my nginx config as suggested in these logs and unreachable endpoint.
[j@elk:~]$ tail -1 /var/log/nginx/error.log
2024/09/08 22:28:44 [emerg] 286614#286614: invalid number of arguments in "allow" directive in /nix/store/mhj7zpk5p8mflyhcmfhyxxx0ky1w815n-nginx.conf:136
That wraps up deploying nextcloud to production for myself.
Future Work
This was more effort with less support than I would have hoped.
- abstract container ip; declarative build avoiding manual
rm /var/lib/nextcloud/config/config.php && systemctl restart nextcloud-setup.service
- implement and test data and DB backup and restore processes
- config
services.nextcloud
to bind to a programmer-defined port if possible - more layers of encryption