I was in similar shoes (my server is running Debian, as it has been for the past two decades), and am going to rebuild it on something else. I chose NixOS, which I recently switched to on my desktop, because it lets me configure the entire system declaratively, even the containers. The major advantage of a declarative configuration is that it will never be out of date.
My main reason for switching is that I’ve been running the server for a good few years, initially maintained via ansible, but that quickly turned into a hellish bash-in-yaml soup that never quite worked right. So I just made changes directly. And then I forgot why I made a change, or had the same thing copy & pasted all over the place. Today, it’s a colossal mess. With NixOS, I can’t make such a mess, because the entire system is declared in one single place, my configuration.
Like you, I also planned to use containers for most everything, but… I eventually decided not to. There’s basically two things that I will run in a container: Wallabag (because it’s not so well integrated into NixOS at the moment), and my Mastodon instance (which runs glitch-soc, which is considerably easier to deploy via the official containers). The rest will run natively. I’ll be hardening them via systemd’s built-in stuff, which will give me comparable isolation without the overhead of containers. Running things natively helps a lot with declarative configuration too, a nice bonus.
For reference, you can find my (work in progress) server configuration here. It might feel a bit overwhelming at first, because it’s written in a literate programming style using org mode & org roam. I found this structure to work great for me, because my configuration is thoroughly documented, both the whys and hows and whats.
Better NixOS integration, less resources used, similar levels of containment. The containers I planned to use don’t provide any additional safety than the system services. In many cases, I could harden the system services more. Like, if a container has a /bin/bash in it, it’s hard to remove that, while I can pretty easily prevent my systemd service from accessing it.
Like,
systemd.services.<name>.confinement
is pretty darn strong. If enabled, NixOS will set up a tmpfs-based chroot with just the required runtime store paths for the service. Good luck doing something similar in a container!