Typically, every project needs its own set of tools and dependencies. Go projects require specific Go versions, infrastructure projects need particular Terraform releases, and you may have multiple projects that require different versions of the same tool.
The traditional approach involves using version managers (asdf, gvm, tfenv, nvm) or relying on everyone on the team to have the correct versions installed. Version managers often require manual switching between projects, and global installations inevitably conflict across different projects.
Nix flakes solve this by declaring your exact project dependencies in a flake.nix
file. When you enter the project directory, the right environment is automatically set up. No global installs, no version conflicts, and it works identically for everyone on any machine.
Here's a typical flake.nix
:
{
description = "Development environment for my-project";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
nixpkgs-terraform.url = "github:stackbuilders/nixpkgs-terraform";
};
nixConfig = {
extra-substituters = "https://nixpkgs-terraform.cachix.org";
extra-trusted-public-keys = "nixpkgs-terraform.cachix.org-1:8Sit092rIdAVENA3ZVeH9hzSiqI/jng6JiCrQ1Dmusw=";
};
outputs = { nixpkgs, flake-utils, nixpkgs-terraform, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
};
goVersion = "1.24";
go = pkgs."go_${builtins.replaceStrings ["."] ["_"] goVersion}";
# Pin specific Terraform version
terraform = nixpkgs-terraform.packages.${system}."1.13.1";
in
{
devShells.default = pkgs.mkShell {
buildInputs = [
# Go
go
pkgs.gotools
pkgs.gopls
# Terraform
terraform
pkgs.terraform-ls
pkgs.tflint
# Cloud tools
pkgs.awscli2
# Dev tools
pkgs.just
pkgs.direnv
];
shellHook = ''
echo "🚀 Development environment loaded"
echo "Go version: $(go version)"
echo "Terraform: $(terraform version)"
'';
};
});
}
What This Gets You
When you cd into the project directory (with direnv configured):
- Installs exact versions of Go (1.24) and Terraform (1.13.1)
- Makes them available only in this shell session
- Doesn't pollute your global system
- Works identically on macOS and Linux
- Works identically for anyone who clones the repo
Pinning Versions
The key is how versions are pinned. For Go:
goVersion = "1.24";
go = pkgs."go_${builtins.replaceStrings ["."] ["_"] goVersion}";
This dynamically constructs go_1_24
from the version string.
For Terraform, use nixpkgs-terraform
to pin specific versions:
terraform = nixpkgs-terraform.packages.${system}."1.13.1";
This gives you exact version control without manually tracking package names.
Direnv Integration
Add a .envrc
file to load the environment when you cd into the project automatically:
use flake
dotenv
Now, the environment is activated automatically, and your .env
file is loaded.
Getting Started
- Install Nix with flakes enabled:
sh <(curl -L https://nixos.org/nix/install)
- Enable flakes in
~/.config/nix/nix.conf
:
experimental-features = nix-command flakes
Add a
flake.nix
to your project (use the example above)Add a
.envrc
file:
use flake
dotenv
- Run
direnv allow
When you cd into the project, you'll see:
$ direnv allow
🚀 Development environment loaded
Go version: go version go1.24.3 darwin/arm64
Terraform: Terraform v1.13.1
That's it. You now have a reproducible development environment that works consistently on any machine.
Top comments (0)