Self hosted Nix binary cache with Attic

Posted on May 19, 2026 at 19:35

In nixpkgs, most of derivations are cached on cache.nixos.org. This is great when consuming derivations on a NixOS system or a home manager configuration, but when you start writing your own derivations which will not end up on nixpkgs, you might spend a lot of time waiting on those derivations to build, especially when automating stuff like CI/CD, servers, etc. The goal is to build everything only once, and reuse what was built on any machine.

Fortunately, you can spin up your own binary cache and every time you build a derivation, push it to the cache. Finally, configure nix to mark your cache as trusted to use those cached derivations.

Install the cache

You will need a NixOS machine. Additionally, attic can work with a S3 bucket and a PostgreSQL database. If you need to use S3 and want to self host it, I recommend Garage.

Credentials

A JWT secret is required for user authentication, you will need to generate one:

nix-shell -p openssl --run "openssl genrsa -traditional 4096 | base64 -w0"

Afterwards, put that secret as the value of the environment variable ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64. You can put it inside a file (so the file will contain ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64=foo where foo is the secret you just generated), but make sure only root can see it. Or you can encrypt it with something like agenix.

Configuration

Nothing special here.

{
  services.atticd = {
    enable = true;
    environmentFile = "/etc/atticd.env"; # Your file might be somewhere else
    settings = {
      listen = "127.0.0.1:8080";
    };
  };
}

Reverse proxy

If you intend to have your cache open to the internet, put it behind a reverse proxy with a TLS certicate. I personally use nginx, it works great and thanks to nix I can configure it without being an expert in the nginx configuration format.

One thing to note if you use a reverse proxy: you need to bump the clientMaxBodySize setting otherwise you will not be able to push derivations (the default is 10m for nginx on NixOS).

let
  fqdn = "attic.domain.example";
in
{
  services.atticd.settings.api-endpoint = "https://${fqdn}/"; # Must end with a trailing slash
  services.nginx = {
    virtualHosts."${fqdn}" = {
      forceSSL = true;
      enableACME = true;
      locations."/" = {
        proxyPass = "http://127.0.0.1:8080";
        proxyWebsockets = true;
      };
    };
    clientMaxBodySize = "1024m";
  };
}

Configure the cache

Create a user token

If you point your web browser to the FQDN your provided earlier, you should see a happy computer prompting you to run attic push. That means the installation worked, you can close your browser. You are ready to create your first cache.

It took me a while to understand the authn/authz model, but there are 2 commands to use:

  • As the administrator, you run atticd-atticadm on the server running atticd to generate tokens which you give to users. When generating a token, you befine what are the token can do.
  • As a user, you run attic login with a name for the cache, the FQDN, and the token generated by the administrator.

On the server, let's create the first token which will give all the possible permissions to the user for one hour:

atticd-atticadm make-token \
    --sub root \
    --validity '1 hour' \
    --pull '*' \
    --push '*' \
    --delete '*' \
    --create-cache '*' \
    --configure-cache '*' \
    --configure-cache-retention '*' \
    --destroy-cache '*'

In this case, the '*' mean that the permission is given for all caches. For examaple, to create a token which can pull indefinitely only from cache hello, run:

atticd-atticadm make-token \
    --sub hello-user \
    --validity '99 years' \
    --pull 'hello'

Consume token

Our regular computer, let's connect to the cache:

nix-shell -p attic-client
attic login mycache https://attic.domain.example eyJh...
cat .config/attic/config.toml # The credentials will show up here

We can now create a cache:

attic cache create hello

If you get the message ✨ Created cache "hello" on "mycache" that means you are good, otherwise the error would be Error: Unauthorized: Unauthorized..

To show information about a cache such as the public key, run:

attic cache info hello

To make the cache publicly available (meaning, no token is required to pull data), run:

attic cache configure --public hello

Use the cache

Make sure you create a token which only allows to pull from the cache, to prevent unwanted pushes.

Now that you have the public key and the binary cache endopoint, you can tell nix to use your cache, for example on NixOS you can use this config:

{
  nix.settings = {
    substituters = [
      "https://attic.domain.example/hello"
    ];
    trusted-public-keys = [
      "hello:1aL6vcQ7mKF0oEXlHsV330qXTMfafsN2radupW8OSMM="
    ];
  };
}

Alternatively you can also use attic use hello, which will write into $HOME/.config/nix/nix.conf for you.

Push to the cache

If you have a token which can push, you can simply use attic push hello <path> to push to your hello cache, for example:

attic push hello /nix/store/...
attic push hello ./result
attic push hello $(nix-build)

CI

With GitHub actions compatible CI, the attic-action works great. You do not even need to specify what needs to be pushed, everything will be pushed at the end of the workflow run.