I’ve been distro-hopping for well over a few years, and during the course of my journey, I’ve tinkered with everything from Debian and DietPi to Arch, Slackware, and beyond. But when it comes to weird yet functional Linux distributions, few can keep up with the sheer quirkiness of NixOS. It’s equal parts ingenious, feature-rich, and mind-bogglingly difficult, and this holy trifecta is what lured me to the NixOS ecosystem.
During my recent escapades with a NixOS virtual machine, I tried running a Docker image via the oci-container module. A few searches later, I arrived at the NixOS container module, which can run containers the same way as Docker, except it integrates well with the underlying NixOS environment. Now that I’ve spent a couple of hours running NixOS containers, I must admit that I’m quite impressed by their capabilities.
Apple Container turned my Mac into a self-hosting war machine
It's hands-down the most resource-efficient method for running containers on macOS
What are NixOS containers?
And how do they function?
Like Docker and Podman, NixOS containers are lightweight, (fairly) isolated environments that let you deploy applications without the extra processing load of virtual machines. But instead of relying on a container runtime, NixOS containers leverage the systemd-nspawn service and integrate with the distro’s quirky nature to help you self-host your services.
Likewise, rather than using Dockerfile configs, NixOS containers can be declared inside the /etc/nixos/configuration.nix file. Once you’ve added the container code to the config file, running the nixos-rebuild switch command will cause the OS to grab the necessary Nix packages and deploy them inside a containerized environment.
For instance, I modified this code from the official wiki to declare a Nextcloud instance as a NixOS container;
networking.nat = {
enable = true;
internalInterfaces = ["ve-+"];
externalInterface = "ens18";
enableIPv6 = false;
};
containers.nextcloud = {
autoStart = true;
privateNetwork = false;
config = { config, pkgs, lib, ... }: {
services.nextcloud = {
enable = true;
package = pkgs.nextcloud31;
hostName = "localhost";
config.dbtype = "sqlite";
config.adminpassFile = "${pkgs.writeText "adminpass" "myrandomepassword"}";
};
system.stateVersion = "23.11";
networking = {
firewall = {
enable = true;
allowedTCPPorts = [ 80 ];
};
# Use systemd-resolved inside the container
useHostResolvConf = lib.mkForce false;
};
services.resolved.enable = true;
};
}; For folks who prefer the old-fashioned imperative (or rather, command-based) setup, you can use the nixos-container command to manage your NixOS-based containerized service fleet – just like you would with the docker keyword.
Why I prefer NixOS containers to their Docker equivalents
They pair well with NixOS’ declarative nature
As a NixOS fanboy, its declarative approach to package installation and system configuration is my favorite aspect of the distribution. For the uninitiated, NixOS uses a central configuration file that contains everything from the apps and settings to the users and their privilege levels, and you’re free to modify it to your heart’s content.
So, instead of executing multiple commands just to run an application, I can add the nixpkg version and app-specific settings inside the configuration.nix file. Afterward, I can rebuild the distribution to match the changes made to the configuration file. NixOS containers work the same way, and I can effectively create a containerized empire inside the same config file instead of switching between docker-compose.yml configurations.
Then there’s the whole backup benefit of such a setup. Rather than looking into Ansible playbooks or self-hosting dedicated snapshot services, I can simply back up the central configuration.nix file and use it to rebuild my NixOS distro as many times as I want. Plus, the configuration isn’t exclusive to a single NixOS instance either, and I can migrate it to another bare-metal or VM setup to rebuild my arsenal of containers from scratch.
Solid compatibility with the nixpkg library
Good ol’ apt and yum package managers are undoubtedly great for the majority of Linux users, but the nixpkg repository knocks them out of the park when it comes to sheer package volume. The best part? NixOS containers share the Nix store with the host machine, allowing access to the massive nixpkg library.
Sure, Docker may have easy-to-deploy images, but Nix containers are far easier to update, since I can essentially point to the updated version of the repo and rebuild my NixOS environment to match the latest package version.
systemd-nspawn makes it easy to manage containers
Another hidden perk of NixOS containers is their reliance on systemd-nspawn, which lets me manage them via systemctl and machinectl commands. Besides checking the detailed status of the containers, I can also modify their configuration file to set up automatic restart. Then there’s the whole resource allocation and integrated logging services offered by systemd, which further simplify container management.
That said, NixOS containers have some drawbacks
Steep learning curve
With its OCI-compatible image collection and massive set of tutorials, Docker is the most approachable container runtime out there, and it’s by far the ideal method for jumping into the self-hosting rabbit hole as a beginner.
In contrast, NixOS containers have an obscenely high difficulty curve, to the point where I would’ve stayed away from them if I wasn't familiar with this quirky distro. That’s because the declarative approach, which is the main highlight of this setup, requires some degree of familiarity with the Nix language. Heck, despite running a NixOS setup for ages, I had trouble getting the container environment working the first couple of times.
Major security issues
If you’re planning to expose your services to the Internet, NixOS containers’ root privileges are a major security flaw. Technically, it’s possible to deploy unprivileged containers, but they have their own share of troubles. The bindMounts parameter is inoperational on an unprivileged NixOS container. Likewise, you can’t really use the root-login argument with the nixos-container command for an unprivileged instance.
Limited to the NixOS ecosystem
As you may have guessed from the name, the biggest drawback of NixOS containers is that they’re restricted to this quirky distribution. Unlike Docker, Podman, and other container runtimes, you can’t really deploy NixOS-powered services on another distribution. So, if you’re a Debian/Arch Linux lover and don’t plan to use NixOS besides the occasional experiment, it doesn’t make a lot of sense to spend hours familiarizing yourself with the Nix language when you’re going to ditch the distro anyway.
Nevertheless, NixOS containers are a fun way to self-host services
As much as I love NixOS containers, they’re clearly not for everyone, and if you’re a casual self-hosting enthusiast, it’s better to stick with Docker or (better yet) a containerization platform. I rely on a mixture of Arch Linux, NixOS, and Windows 11 dev environments for my coding projects. So, I’ve got enough incentive to mess around with NixOS containers and (hopefully) gain some more insight into the nitty-gritty of this wacky distro after spending hours troubleshooting a broken config file.
Hear me out – a GUI distribution isn’t all that bad for an entry-level home server
Yup, running a Debian server with a desktop environment is perfectly valid when you're new to the home lab domain
