Containers and images
Nix gives you two different container stories, and they solve different problems. Declarative NixOS containers run a slice of NixOS next to your host, managed by the same config. OCI images package an application into a portable artifact you can ship to any Docker or Podman runtime. Pick the first when you want isolated services on a NixOS machine you control. Pick the second when you need an image that runs somewhere else.
Declarative NixOS containers
A NixOS container is a lightweight system container declared right in
configuration.nix. Each one is a small NixOS instance with its own services,
built and activated alongside the host.
{
containers.web = {
autoStart = true;
config = { config, pkgs, ... }: {
services.nginx.enable = true;
services.nginx.virtualHosts."localhost".root = "/var/www";
networking.firewall.allowedTCPPorts = [ 80 ];
system.stateVersion = "24.11";
};
};
}By default a container shares the host network. To give it an isolated network
with a known address, use privateNetwork with a host and local address.
{
containers.web = {
autoStart = true;
privateNetwork = true;
hostAddress = "192.168.100.1";
localAddress = "192.168.100.2";
config = { pkgs, ... }: {
services.nginx.enable = true;
system.stateVersion = "24.11";
};
};
}After a rebuild, manage containers with the nixos-container command.
# list containers and their status
nixos-container list
# start, stop, and open a root shell
sudo nixos-container start web
sudo nixos-container stop web
sudo nixos-container root-login webThese containers rebuild with the host. When you run
nixos-rebuild switch --flake .#nixos-host, the container's config is evaluated
and updated too.
Building OCI images with dockerTools
When you need an image that runs on a Docker or Podman host anywhere, build one
from Nix with dockerTools. buildLayeredImage produces an image where each
store path becomes its own layer, so rebuilds reuse unchanged layers.
# in a flake's outputs, with pkgs for x86_64-linux
{
packages.x86_64-linux.image = pkgs.dockerTools.buildLayeredImage {
name = "hello-web";
tag = "latest";
contents = [ pkgs.python3 ];
config = {
Cmd = [ "${pkgs.python3}/bin/python3" "-m" "http.server" "8080" ];
ExposedPorts."8080/tcp" = { };
};
};
}Build the image and load it into a local Docker or Podman daemon.
nix build .#image
docker load < result
docker run -p 8080:8080 hello-web:latestThe output is a normal OCI image. Nothing about it depends on Nix at runtime, so it runs on any host with a container engine.
Running other people's containers
If you just want to run an existing image on a NixOS host, declare it with
virtualisation.oci-containers. Choose podman or docker as the backend.
{
virtualisation.podman.enable = true;
virtualisation.oci-containers = {
backend = "podman";
containers.grafana = {
image = "grafana/grafana:latest";
ports = [ "3000:3000" ];
volumes = [ "grafana-data:/var/lib/grafana" ];
};
};
}NixOS turns each entry into a systemd service, so the container starts on boot and restarts on failure like any other service.
Advanced
buildLayeredImage shares layers across images that use the same store paths, so
a fleet of images built from one nixpkgs deduplicates most of their size on a
registry. For very large images, streamLayeredImage writes the tarball to
stdout instead of into the store, which avoids keeping a second copy on disk.
nix build .#image # uses buildLayeredImage, image lands in the store
# streamLayeredImage variant pipes straight to the daemon
nix run .#streamImage | docker loadNext
For packaging the application that goes inside an image, see Writing packages. For complete host configs see Examples, for shipping to a server see Deploying to remote machines, and for the flake outputs that expose an image see Flakes.