← Back to list

From Migration Anxiety to One-Command Setups: Why I Bet My Dev Environment on Nix

As software engineers, we don’t just write code. We curate ecosystems.

Ganesh · 2026-05-07 06:52 · 4 claps · 4.1 min read
#software-development #productivity #nix #configuration-management #migration
Open on Medium ↗
Wiki topics: BIZ · Business Strategy 💻 · Programming ⏱️ · Productivity 🧠 · Mental Wellness 🥊 · Combat Sports

From Migration Anxiety to One-Command Setups: Why I Bet My Dev Environment on Nix

nix package manager

nix package manager

As software engineers, we don’t just write code. We curate ecosystems.

My machine runs hundreds of tools: language runtimes, CLI utilities, shell plugins, container runtimes, linters, formatters, database clients, and a sprawling ~/.config directory. Over years of projects, upgrades, and trial-and-error, my environment became a fragile house of cards. It worked because I knew exactly which commands I ran and which PATH entries I forgot to document.

Then came the hardware shift: Intel Mac → Apple Silicon Mac.

macOS offers Migration Assistant, and for most users, it’s brilliant. It copies apps, preferences, and user data seamlessly. But for engineers? It’s a minefield.

The Migration Problem: Why Migration Assistant Isn’t Enough

Migration Assistant doesn’t understand dev toolchains. It copies /usr/local and ~/Library blindly, which breaks immediately on Apple Silicon because Homebrew changed its prefix to /opt/homebrew. Rosetta 2 translates most x86_64 binaries flawlessly, but adds ~10–30% overhead, fails for native C-extensions, and doesn’t touch architecture-specific caches or virtual environments.

Suddenly, your python venvs are broken, node global packages point to missing native addons, terraform plugins need reinitialization, and your shell profile references binaries that no longer exist. You’re left with two choices:

  1. Spend a weekend manually reinstalling, debugging, and reconfiguring everything.
  2. Hope the migration “just works” and deal with cryptic errors six months later.

I chose neither. I wanted a system where my environment wasn’t a collection of imperative shell commands, but a single, version-controlled source of truth.

Enter Nix: Declarative, Reproducible, Cross-Arch

Nix is fundamentally a package manager, but treating it as just that undersells what it enables. Out of the box, Nix excels at declaratively installing binaries, resolving dependencies, and keeping them isolated. It solves the “how do I install this tool consistently across machines?” problem. But my real headache wasn’t just about fetching software — it was the sprawling, fragile ecosystem of user-level configuration: dotfiles, shell aliases, editor plugins, language runtimes, IDE settings, and environment variables. Scattered across ~/.config, ~/.zshrc, and hidden caches, this state was impossible to version-control or reproduce cleanly.

That’s where home-manager comes in. While Nix manages the packages, home-manager manages the user environment. It’s a Nix-based tool that declaratively configures your home directory: it symlinks config files, wires shell profiles, sets up editor plugins, manages environment variables, and ties application-specific settings together. By combining flakes, nixpkgs, and home-manager, I can define my entire toolchain and personal configuration in a single, version-controlled flake.nix. Then, on any machine:

git clone my-dotfiles.git
cd my-dotfiles
nix run .#homeConfigurations.<username>.activationPackage

That’s it. Same tools. Same versions. Same configs. On Intel Mac, ARM Mac, x86 Linux, or a Raspberry Pi.

How I Actually Use It

Here’s a stripped-down version of my real flake.nix. It uses nixpkgs + home-manager in standalone mode (no NixOS required), pins exact versions, and explicitly supports multiple architectures.

{
  description = "My declarative dev environment";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
    home-manager = {
      url = "github:nix-community/home-manager/release-24.11";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { nixpkgs, home-manager, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs { inherit system; };
      in {
        homeConfigurations = {
          jdoe = home-manager.lib.homeManagerConfiguration {
            inherit pkgs;
            extraSpecialArgs = { inherit system; };
            modules = [ ./home.nix ];
          };
        };
      }
    );
}

And home.nix:

{ pkgs, system, ... }:

let
  # Cross-arch helper: pick packages based on system
  isDarwin = pkgs.stdenv.hostPlatform.isDarwin;
  isLinux = pkgs.stdenv.hostPlatform.isLinux;
in
{
  # Core shell & editor
  home.packages = with pkgs; [
    git
    neovim
    zsh
    starship
    ripgrep
    fd
    jq
    yq

    # Dev toolchains
    nodejs_20
    python311
    go
    terraform
    docker-compose

    # macOS/Linux specific
    (if isDarwin then mas else null)
    (if isLinux then gcc else null)
  ];

  # Shell configuration
  programs.zsh = {
    enable = true;
    oh-my-zsh = {
      enable = true;
      plugins = [ "git" "zsh-autosuggestions" "zsh-syntax-highlighting" ];
    };
    shellAliases = {
      gs = "git status";
      ga = "git add -A";
      gc = "git commit -m";
    };
  };

  # Editor setup
  programs.neovim = {
    enable = true;
    defaultEditor = true;
    vimAlias = true;
    plugins = with pkgs.vimPlugins; [
      telescope-nvim
      nvim-treesitter
    ];
  };

  # Environment variables
  home.sessionVariables = {
    EDITOR = "nvim";
    LANG = "en_US.UTF-8";
  };

  # Git config
  programs.git = {
    enable = true;
    userName = "John Doe";
    userEmail = "john@example.com";
    extraConfig = {
      init.defaultBranch = "main";
      core.editor = "nvim";
    };
  };

  # Home directory state
  home.stateVersion = "24.11";
}

Why This Works for Me

  • Pinned versions: nixpkgs/nixos-24.11 + release-24.11 means no surprise updates breaking my workflow.
  • Cross-arch aware: flake-utils.lib.eachDefaultSystem + pkgs.stdenv.hostPlatform lets me conditionally include mas (macOS App Store CLI) or gcc without breaking Linux builds.
  • Idempotent: Run it twice, same result. No duplicate installs, no stale configs.
  • Git-tracked: My environment lives in a repo. Code review my dotfiles? Sure.

Real Talk: It’s Not Magic (Yet)

Nix didn’t save me time on day one. It cost me a weekend learning:

  • The Nix expression language
  • How flakes resolve dependencies
  • Debugging home-manager activation errors
  • Understanding why brew install and nix install don’t play nice together

And there are caveats:

  • Not every package is available on every architecture. Sometimes you need overrideAttrs or fetch from GitHub.
  • home-manager manages user-space tools. System-level services (Docker daemon, networking, firewall) require nix-darwin or NixOS.
  • You’ll still need to migrate GUI apps manually. Nix excels at CLI/toolchain reproducibility, not App Store or .app bundles.

But the ROI compounds. Every new machine, every OS upgrade, every homelab node now takes < 10 minutes to reach parity. The mental overhead of “will this break after the next macOS update?” is gone.

Final Thoughts

If you’re a developer with a sprawling toolchain, migrating between architectures, or planning a homelab, Nix isn’t just a package manager. It’s an insurance policy against environment drift.

It asks for upfront investment in learning and refactoring. But in return, it gives you something rare in engineering: predictability.

Your machine becomes a function. Input: flake.nix. Output: your workspace. Run it anywhere.


메타데이터
post_id
0ac188497ecf
slug
from-migration-anxiety-to-one-command-setups-why-i-bet-my-dev-environment-on-nix-0ac188497ecf
url
https://medium.com/@ganesh3075/from-migration-anxiety-to-one-command-setups-why-i-bet-my-dev-environment-on-nix-0ac188497ecf
canonical_url
https://medium.com/@ganesh3075/from-migration-anxiety-to-one-command-setups-why-i-bet-my-dev-environment-on-nix-0ac188497ecf
author_url
https://medium.com/@ganesh3075
status
ok
fetched_at
2026-06-13 00:08:42