Is it possible to use nix in a project but without putting all the code in nix store?

For example, I have some code and some secret in a git repo.
The secrets are managed by SOPS (I think sops-nix is only for NixOS)
I want to build the code with the secret, using nix
Is it possible to make it without /nix/shore involve in this project?

I know if the code is defined as package/derivation and run with nix build , it definitely end up into /nix/store
But how about something like nix shell or nix develop to create the environment suitable to build /run the code in the repo? Are those code also end up into /nix/store?
How do I know/check it?

What is the good way for this purpose?
Thanks

Can you clarify why you want to avoid /nix/store - do you not have access to install nix systemwide? Or is it for bundling purposes to ship e.g. a static binary? Or do you just want to be able to acess the build result in the project dir?

And no if you start a shell and run non-nix commands, those will not output into the store - only nix-daemon can write to the store.

I just want to avoid the secrets in the project go into /nix/store

that is exactly what sops-nix is for

More specifically, the encrypted secrets will be in the store, decryption happens at runtime.

The secrets can also be stored outside the store with sops-nix, but indeed they will only be accessible at runtime.

Whether that’s useful depends on your use case. What is your project? It’s possible you want something like the netrc support feature instead.

Curious observer here. Would you mind elaborating and/or link to the relevant documentation for how sops-nix can be setup to bypass the store? I still need to get around to configuring some kind of secrets management that isn’t completely done out-of-band and when I last tried to parse through the sops-nix docs I don’t remember seeing that as a potential choice, but there was a lot to go through and it was balanced between learning other NixOS things.

The sops file is a lib.types.path, not a store path, so all paths are valid. It might be mentioned somewhere in the readme, but I don’t spot an explicit explanation of the type at a glance.

But yeah, just set it to any ol’ path (and define it as a string so that nix does not attempt to read it) and it works as long as you physically put the sops file there. After that the file is decrypted from that location at runtime, so your secrets never hit the store or git.

This blog post about what may well become the future built-in secrets manager may be of interest if you want to figure out how this stuff works under the hood. Sops is hardly required, and really the secrets don’t need to be encrypted either, that’s all just the result of sops/agenix becoming dominant early on. Shame the PR hasn’t seen much attention since the announcement, it’d be great if newcomers didn’t have to use third party projects and learn about sops/encryption on top of all the other stuff.

1 Like

I am just thinking if this use case is possible

Take this as an example:
Some documents in .md file is writted in a git repo, and the flake.nix have devShell defined and it can use pandoc to convert them into other format like pdf.
This repo nix flake should also be able to run in other distro like Arch with nix installed.
In one of the .md file, there is a secret. I want to encrypt that particular file, so only people can decrypt it can see its content.
There are some library out there can convert .md into .json, then I can use SOPS in that json file.
I can also use git-crypt instead of SOPS to encrypt the whole file.

I think when I decrypt the file, then use the devShell by nix develop before running pandoc, all the decrypted file just copied into /nix/store

Yes, but this is a misfeature of flakes. If you don’t use flakes but a traditional shell.nix with nix-shell instead you don’t have this problem at all.

This also doesn’t happen if the decrypted file is not checked into git, assuming your project uses git. Flakes will filter what is put in /nix/store for content tracked by git.

Note that if you use nix-build or nix build, and that file with a secret is part of the build inputs, it will still end up in the nix store.

Explanations of how this would work aside, I would argue that you’re making some significant architectural mistake somewhere if you’re commiting files with secrets in them to git repositories in the first place (except maybe when using sops in some cases). Secrets are data, and should not be hard-coded into source code, but supplied as data via other channels (like password managers, kubernetes vault implementations, sops, …). If you follow this principle, you never run into this problem to begin with.

1 Like

Oh very interesting! Thanks for taking the time to explain!

After that the file is decrypted from that location at runtime, so your secrets never hit the store or git.

“At runtime” in this case means on switch-to-configuration right? In other words, say I make a new sops file and point my configuration at it in such as way that nixos-rebuild switch makes a generation 100 and decrypts the out-of-store sops file at that time. If I then switch to generation 99, delete or move the sops file and switch back to generation 100, then the activation of generation 100 at that point will be missing the necessary secrets right? Or is it that on each systemd start <needs-secret>.service there is a decryption step?

Thanks in advance for your help on this. Still getting my mind around the ideas involved in declarative secrets management.

The secrets are deployed to /run/secrets independently of services that would consume them.

1 Like

Thanks! I think that means in my Generation 100-99-100 example, the secrets would actually persist through those different system unless a reboot occurred? And as a corollary, if I just stayed on Generation 100 but deleted or moved the out-of-store sops file with the the secrets, everything would keep working fine until the next reboot, which would be missing the expected /run/secrets secrets?

To be clear, following scenario:

{ config, ... }: {
  sops = {
    defaultSopsFile = "/etc/sops/secrets.yaml";

    sops.secrets.password = { };
  };

  users.mysql.passwordFile = config.sops.secrets.password.path;
}

For your example:

  1. nixos-rebuild switch → create generation 100
    • This will create a file under /run/secrets containing that secret
    • Your mysql configuration will read this file when it starts to figure out what the mysql password should be
  2. You reboot into generation 99
    • Since the sops configuration did not yet exist, the sops service to deploy your secrets does not run so there are no secrets in /run/secrets
    • If you switch to generation 99 without rebooting, the secrets stay in /run/secrets because there is no state management, but switching generations without rebooting when making such large changes is asking for trouble anyway, it results in inconsistent systems
  3. You delete /etc/sops/secrets.yaml
  4. You reboot into generation 100
    • The secrets file is missing, so sops fails to deploy your secrets
    • Mysql cannot find its password file and fails to start (or something)

If you didn’t reboot, everything would be fine™ until your next reboot - the secrets persist in /run/secrets, since there is no state management, but you would see an error in your activation script logs since sops would fail to deploy your secrets.

If you want your secrets to be tied more closely to systemd services, you can achieve that with systemd credentials instead. NixOS modules generally don’t have great support for those, though, you’d be stuck modifying the systemd services directly most of the time.

3 Likes