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-atticadmon the server runningatticdto 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 loginwith 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.