Nix binary cache implemented in rust (maintainer: @Mic92)
  • Rust 96.3%
  • Nix 2.1%
  • HTML 0.5%
  • Python 0.5%
  • Dafny 0.4%
  • Other 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
github-actions[bot] 7c1ef262e3
Merge pull request #1173 from nix-community/dependabot/nix/nix-61b6332
chore(deps): bump nix from `27206e8` to `61b6332`
2026-08-23 16:21:12 +00:00
.cargo build: keep frame pointers in cargo builds 2026-04-19 17:17:23 +02:00
.github chore(deps): bump actions/checkout from 6 to 7 2026-06-21 16:02:02 +00:00
docs/architecture store-gc: add crate skeleton for garbage collection 2026-08-19 11:21:59 +02:00
fuzz Split store-core into store-{content-addres,derivation} 2026-05-16 13:25:00 -04:00
harmonia-bench bench: add ref-scan streaming benchmark 2026-07-06 11:39:08 +02:00
harmonia-cache Merge pull request #1139 from StarryReverie/fix-ranged-200-http-response 2026-08-15 12:21:32 +00:00
harmonia-client drop readme field from crate manifests 2026-06-01 11:25:53 +02:00
harmonia-daemon protocol: add SubmitOutput op 2026-07-21 16:36:55 -04:00
harmonia-file-core Always serialize executable field, matching latest Nix 2026-05-11 18:02:42 -04:00
harmonia-file-nar file-nar: replace mmap with chunked reads in NAR dumper 2026-08-15 15:12:32 +02:00
harmonia-protocol store: add recursive hashDerivationModulo for unresolved derivations 2026-08-05 01:22:08 -04:00
harmonia-protocol-derive harmonia-protocol-derive: adapt to syn 3.0 API 2026-08-15 14:23:21 +02:00
harmonia-ssh-store drop readme field from crate manifests 2026-06-01 11:25:53 +02:00
harmonia-store-aterm harmonia-store-aterm: cap proptest cases for derivation tests 2026-08-19 09:29:43 +02:00
harmonia-store-build-result drop readme field from crate manifests 2026-06-01 11:25:53 +02:00
harmonia-store-content-address protocol: add AddToStoreScanning op 2026-07-17 16:46:11 -04:00
harmonia-store-db store-db: add bulk path invalidation and freelist-aware vacuum 2026-08-19 10:02:05 +02:00
harmonia-store-derivation store: add recursive hashDerivationModulo for unresolved derivations 2026-08-05 01:22:08 -04:00
harmonia-store-fs store-fs: add filesystem layout crate for the local store 2026-08-19 11:14:46 +02:00
harmonia-store-gc store-gc: add root discovery and temp-root registration 2026-08-19 13:34:44 +02:00
harmonia-store-nar-info harmonia-store-nar-info: add a textual narinfo parser 2026-06-16 20:47:08 -04:00
harmonia-store-path store-fs: add filesystem layout crate for the local store 2026-08-19 11:14:46 +02:00
harmonia-store-path-info drop readme field from crate manifests 2026-06-01 11:25:53 +02:00
harmonia-store-ref-scan store-ref-scan: elide per-byte bounds checks in the hot scan loop 2026-07-06 11:39:08 +02:00
harmonia-store-remote protocol: add SubmitOutput op 2026-07-21 16:36:55 -04:00
harmonia-utils-base-encoding utils: add HashReader, rename HashSink to HashWriter, and export base32 ALPHABET 2026-06-06 15:39:14 -04:00
harmonia-utils-hash utils: add HashReader, rename HashSink to HashWriter, and export base32 ALPHABET 2026-06-06 15:39:14 -04:00
harmonia-utils-io drop readme field from crate manifests 2026-06-01 11:25:53 +02:00
harmonia-utils-signature Split store-core into store-{content-addres,derivation} 2026-05-16 13:25:00 -04:00
harmonia-utils-test drop readme field from crate manifests 2026-06-01 11:25:53 +02:00
nix flake: drop x86_64-darwin support 2026-08-15 14:57:16 +02:00
scripts store-gc: add crate skeleton for garbage collection 2026-08-19 11:21:59 +02:00
tests flake: drop flake-parts and gather nix files under nix/ 2026-04-18 13:58:37 +02:00
.envrc add envrc 2023-01-28 11:43:42 +01:00
.gitignore Add upstream worker protocol wire test suite 2026-05-12 15:59:40 -04:00
.taplo.toml Format TOML files with taplio 2026-03-29 12:57:18 -04:00
Cargo.lock Merge pull request #1172 from nix-community/dependabot/cargo/blake3-1.8.7 2026-08-23 16:16:26 +00:00
Cargo.toml store-gc: add crate skeleton for garbage collection 2026-08-19 11:21:59 +02:00
codecov.yml codecov: move comment 2025-11-29 22:39:57 +01:00
config.vcl get rid of libnixstore 2024-11-25 11:50:48 +01:00
CONTRIBUTING.md flake: move clippy from packages to checks 2026-04-18 14:04:40 +02:00
flake.lock Merge pull request #1173 from nix-community/dependabot/nix/nix-61b6332 2026-08-23 16:21:12 +00:00
flake.nix flake: drop x86_64-darwin support 2026-08-15 14:57:16 +02:00
harmonia.toml get rid of libnixstore 2024-11-25 11:50:48 +01:00
LICENSE Add code from Nix Ninja 2025-11-27 12:04:23 -05:00
README.md harmonia-cache: enable zstd compression by default 2026-08-15 14:16:23 +02:00

harmonia

Harmonia is a binary cache for nix that serves your /nix/store as a binary cache over http. It's written in Rust for speed.

Features

  • http-ranges support for nar file streaming
  • streaming build logs
  • .ls file streaming
    • Note: doesn't contain narOffset in json response but isn't needed for nix-index
  • Add /serve/<narhash>/ endpoint to allow serving the content of package. Also discovers index.html to allow serving websites directly from the nix store.
  • Content is compressed transparently with zstd.
  • Builtin TLS: when no frontend webserver is used, Harmonia can also provide TLS encryption

Configuration for public binary cache on NixOS

Using NixOS stable (from nixpkgs)

There is a module for harmonia in nixpkgs. The following example set's up harmonia as a public binary cache using nginx as a frontend webserver with https encryption:

{ config, pkgs, ... }: {
  services.harmonia.enable = true;
  # FIXME: generate a public/private key pair like this:
  # $ nix-store --generate-binary-cache-key cache.yourdomain.tld-1 /var/lib/secrets/harmonia.secret /var/lib/secrets/harmonia.pub
  services.harmonia.signKeyPaths = [ "/var/lib/secrets/harmonia.secret" ];
  # Example using sops-nix to store the signing key
  #services.harmonia.signKeyPaths = [ config.sops.secrets.harmonia-key.path ];
  #sops.secrets.harmonia-key = { };

  # optional if you use allowed-users in other places
  #nix.settings.allowed-users = [ "harmonia" ];

  networking.firewall.allowedTCPPorts = [ 443 80 ];

  # FIXME: replace this with your own email
  security.acme.defaults.email = "yourname@youremail.com";
  security.acme.acceptTerms = true;

  services.nginx = {
    enable = true;
    recommendedTlsSettings = true;
    # FIXME: replace "cache.yourdomain.tld" with your own domain.
    virtualHosts."cache.yourdomain.tld" = {
      enableACME = true;
      forceSSL = true;

      locations."/".extraConfig = ''
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_redirect http:// https://;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
      '';
    };
  };
}

Using the flake version (latest features)

To use the latest version with all features, import the harmonia flake:

# flake.nix
{
  inputs.harmonia.url = "github:nix-community/harmonia";
  # ... other inputs
}

# configuration.nix
{ inputs, config, pkgs, ... }: {
  imports = [ inputs.harmonia.nixosModules.harmonia ];

  services.harmonia-dev.cache.enable = true;
  # FIXME: generate a public/private key pair like this:
  # $ nix-store --generate-binary-cache-key cache.yourdomain.tld-1 /var/lib/secrets/harmonia.secret /var/lib/secrets/harmonia.pub
  services.harmonia-dev.cache.signKeyPaths = [ "/var/lib/secrets/harmonia.secret" ];

  # All other nginx configuration remains the same as above
  networking.firewall.allowedTCPPorts = [ 443 80 ];

  services.nginx = {
    enable = true;
    recommendedTlsSettings = true;
    virtualHosts."cache.yourdomain.tld" = {
      enableACME = true;
      forceSSL = true;
      locations."/".extraConfig = ''
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_redirect http:// https://;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
      '';
    };
  };
}

You can use the binary cache on a different machine using the following NixOS configuration:

{
  nix.settings = {
    substituters = [ "https://cache.yourdomain.tld" ];
    # FIXME replace the key with the content of /var/lib/secrets/harmonia.pub
    trusted-public-keys = [ "cache.yourdomain.tld-1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" ];
  };
}

Configuration format

Configuration is done via a toml file. Hint: You don't need to interface with the configuration directly in case you are using the NixOS module. The location of the configuration file should be passed as env var CONFIG_FILE. If no config file is passed the following default values will be used:

# default ip:hostname to bind to
bind = "[::]:5000"
# unix socket are also supported
# bind = "unix:/run/harmonia/socket"
# Sets number of workers to start in the webserver
workers = 4
# Sets the per-worker maximum number of concurrent connections.
max_connection_rate = 256
# binary cache priority that is advertised in /nix-cache-info
priority = 30
# Whether to enable transparent compression with zstd.
# Default: true
enable_compression = true

# Allow to override the store path advertised in /nix-cache-info
# virtual_nix_store = "/nix/store"
# Allow to serve the nix store from a different physical location
# Default: empty
# Example: if you use `nix copy --store /guest` to populate a store than configure:
# real_nix_store = "/guest/nix/store"

# Path to the nix SQLite database. Harmonia reads store metadata directly from
# this file (no nix-daemon connection is used). Derived from the store layout
# by default; override only for non-standard state directories.
# nix_db_path = "/nix/var/nix/db/db.sqlite"

Per default we wont sign any narinfo because we don't have a secret key, to enable this feature enable it by providing a path to a private key generated by nix-store --generate-binary-cache-key cache.example.com-1 /etc/nix/cache.secret /etc/nix/cache.pub

# nix binary cache signing key
sign_key_paths = [ "/run/secrets/cache.secret" ]

Harmonia also reads the SIGN_KEY_PATHS environment variable which holds paths to secret keys separated by spaces. All paths provided by sign_key_paths config option and SIGN_KEY_PATHS environment variable will be used for signing.

TLS Configuration

Harmonia can serve content over HTTPS without requiring a reverse proxy. To enable TLS, specify the paths to your certificate and private key files in the configuration:

# Path to TLS certificate (PEM format)
tls_cert_path = "/path/to/cert.pem"
# Path to TLS private key (PEM format)
tls_key_path = "/path/to/key.pem"

Requirements for TLS certificates:

  • Certificate must be in PEM format
  • Certificate must be X.509 v3 (rustls does not support older versions)
  • Private key can be in either PKCS#8 or RSA format
  • Both files must be readable by the harmonia process

Example generating a self-signed certificate for testing:

openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes \
  -subj "/C=US/ST=State/O=Organization/CN=cache.example.com"

Note: When TLS is enabled, harmonia will only accept HTTPS connections on the configured port.

Logging Configuration

Logging is handled by tracing and configured via the RUST_LOG environment variable using EnvFilter syntax (compatible with the familiar env_logger directives). The default filter is info. To only log errors use RUST_LOG=error, and to keep info messages while disabling actix access logging use RUST_LOG=info,actix_web::middleware=error.

Build

Whole application

nix build -L

Get a development environment:

nix develop

Run tests

nix flake check -L

Prometheus Monitoring

Harmonia exposes Prometheus metrics at the /metrics endpoint for monitoring and observability.

Available Metrics

HTTP Request Metrics:

  • harmonia_http_requests_total - Total number of HTTP requests (labeled by method, path, and status code)
  • harmonia_http_request_duration_seconds - Request latency histogram with buckets ranging from 0.0001s to 1.0s

Grafana Dashboard

A pre-configured Grafana dashboard is available in the repository at harmonia-cache/harmonia-grafana-dashboard.json. This dashboard visualizes:

  • Request rate and latency patterns
  • HTTP status code distribution
  • Error rates and performance trends

You can import this dashboard into your Grafana instance to monitor your Harmonia deployment.

Example Prometheus Configuration

To scrape metrics from Harmonia, add the following to your Prometheus configuration:

scrape_configs:
  - job_name: 'harmonia'
    static_configs:
      - targets: ['your-harmonia-host:5000']

Contributing

See CONTRIBUTING.md for development setup, testing, and code style guidelines. For architecture details, see docs/architecture.

Inspiration