khepri allows you to easily define "container compositions" natively in your NixOS configuration similar to how you would define them in a docker-compose.yaml. This enables your NixOS configuration to become the source of truth for your system, without the need for another orchestration layer on top.
The main use-case for khepri is to easily run containerized workloads on NixOS, when NixOS modules for an application are not available/applicable and running a large container orchestrator like Kubernetes is overkill.
This tool is heavily inspired by compose2nix.
Assuming you are using flakes to configure your NixOS system, you can add the khepri module as follows:
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
khepri = {
url = "github:jrester/khepri/v0.1.1";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, khepri }: {
nixosConfigurations.yourSystem = nixpkgs.lib.nixosSystem {
modules = [ khepri.nixosModules.khepri ];
};
};
}{ pkgs, ... }:
{
# You can choose between 'docker' and 'podman' as backend.
khepri.backend = "docker";
# Define your compositions.
# Each composition would be logically equivialent to a `docker-compose.yml`.
khepri.compositions = {
# Composition for running Nextcloud.
nextcloud = {
networks = {
nextcloud = { };
};
volumes = {
nc_data = {};
pg_data = {};
redis_data = {};
};
services = {
db = {
# Images can be referenced by their name, which will be automatically
# pulled when the service starts up.
image = "postgres:15";
networks = [ "nextcloud" ];
volumes = [ "pg_data:/var/lib/postgresql/data:rw" ];
environment = {
POSTGRES_DB = "nextcloud";
POSTGRES_USER = "nextcloud";
POSTGRES_PASSWORD = "changeme";
};
restart = "unless-stopped";
};
redis = {
image = "redis:7-alpine";
networks = [ "nextcloud" ];
volumes = [ "redis_data:/data:rw" ];
restart = "unless-stopped";
};
app = {
# Images can also be derivations created from `dockerTools.pullImage` or `dockerTools.buildImage`.
# The hash can be obtained through nix-prefetch-docker.
image = pkgs.dockerTools.pullImage {
imageName = "nextcloud";
imageDigest = "sha256:ff2cbaab14c85e587b5541e3aff4216a8a484e06424ebae661581937c0c8da0c";
hash = "sha256-XDbwoTMubzgajpMIiGR5leeQEQYjS3sv0P6Cjkwk4mI=";
finalImageName = "nextcloud";
finalImageTag = "33.0.0-apache";
};
networks = [ "nextcloud" ];
ports = [ "8080:80/tcp" ];
volumes = [ "nc_data:/var/www/html:rw" ];
environment = {
POSTGRES_HOST = "db";
POSTGRES_DB = "nextcloud";
POSTGRES_USER = "nextcloud";
POSTGRES_PASSWORD = "changeme";
REDIS_HOST = "redis";
NEXTCLOUD_TRUSTED_DOMAINS = "nextcloud.example.com";
};
dependsOn = [ "db" "redis" ];
restart = "unless-stopped";
};
};
};
};
}Similar to docker-compose, khepri performs name mangling of service, volume and network names based on the composition name by default. This means that the service named db in the nextcloud composition will be assigned the canonical name of nextcloud_db unless otherwise configured. However, inside a composition, it can still be referenced by its db name, e.g., in depends_on.
Similar to the NixOS built-in virtualization.oci-containers, khepri makes heavy use of systemd to manage the composition and its services, volumes and networks.
For this purpose all services are grouped under a systemd target unit khepri-compose-<composition name>-root.target (e.g., khepri-compose-nextcloud-root.target). This enables orchestration of the whole composition, e.g., to restart the composition as a whole.
Each volume is translated to a systemd service unit khepri-volume-<canonical volume name>.service (e.g., khepri-volume-nextcloud_pgdata.service), which creates the volume on demand. This service unit will be automatically pulled in by each service that references the volume in a composition.
Each network is translated to a systemd service unit khepri-network-<canonical network name>.service (e.g., khepri-network-nextcloud_nextcloud.service), which creates the network on demand. This service unit will be automatically pulled in by each service that references the network in a composition.
Each service is translated to a systemd service unit khepri-service-<canonical service name>.service (e.g., khepri-service-nextcloud_app.service), which creates the container. The service will automatically pull in the required volume, network and service units it requires to ensure that everything is ready, before the container is started.
khepri orientates itself at the features of docker-compose. Currently, a subset of the features of docker-compose are supported:
| Notes | ||
|---|---|---|
image |
✅ | Supports images from dockerTools.pullImage |
container_name |
✅ | |
environment |
✅ | |
volumes |
✅ | |
labels |
✅ | |
ports |
✅ | |
dns |
❌ | |
cap_add/cap_drop |
✅ | |
logging |
❌ | |
depends_on |
Only short syntax is supported. | |
restart |
No 'on-failure:' | |
deploy.restart_policy |
❌ | |
deploy.resources |
❌ | |
devices |
✅ | |
networks |
✅ | |
networks.aliases |
❌ | |
networks.ipv*_address |
❌ | |
network_mode |
❌ | |
privileged |
❌ | |
extra_hosts |
✅ | |
sysctls |
❌ | |
shm_size |
❌ | |
runtime |
❌ | |
security_opt |
❌ | |
command |
✅ | |
healthcheck |
❌ | |
hostname |
❌ | |
mac_address |
❌ |
basic |
✅ |
labels |
❌ |
name |
❌ |
driver |
❌ |
driver_opts |
❌ |
ipam |
❌ |
external |
✅ |
internal |
❌ |
basic |
✅ |
driver |
❌ |
driver_opts |
❌ |
labels |
❌ |
name |
❌ |
external |
✅ |
With the NixOS built-in virtualization.oci-containers option it is already possible to manage containers natively inside the NixOS configuration. In fact, khepri translates to oci-containers natively but adds additional logic around the management of networks, volumes, as well as name mangling and a more sophisticated systemd integration. So, khepri can be seen as an extension of oci-containers, and it is possible to easily plug into the underlying oci-containers definitions.
compose2nix can be used to automatically generate a NixOS configuration from a docker-compose.yaml file. Although, the results of this conversion can be easily integrated into your NixOS configuration, they are very verbose. Changes to your container setup can become quite cumbersome. For example, systemd dependencies must be configured manually, instead of "just" adding a new volume to your container.
In contrast, khepri provides an interface which is syntactically similar to docker-compose and performs the steps done by compose2nix automatically under the hood. Additionally, all of this happens natively in nix, so users do not need to re-run compose2nix when their compose definition changes.
arion is a Nix wrapper around docker-compose, offering a similar experience to docker-compose. Instead of writing a docker-compose.yaml file you would write a arion-compose.nix file and control it using arion <up/down>. Therefore, arion does not provide a native integration with NixOS like khepri does.