These are my dotfiles, managed by Nix, Nix-Darwin and Home Manager (via flakes).
dotfiles/
├── darwin/ # nix-darwin (macOS system config)
| ├── modules/
| | └── homebrew.nix
| ├── profiles/
| | └── ditto.nix
| └── configuration.nix
├── home-manager/
| ├── modules/
| | ├── dev/ # language tooling (nix-lang, rust)
| | ├── ide/vscode* # extensions & settings
| | ├── tools/ # cheat, helix, gh-dash, agents, claude-code
| | ├── common.nix # shared CLI tools (no GUI)
| | └── git.nix
| └── profiles/ # code, dev (devcontainer), home, work
├── lib/
| └── core-packages.nix # packages shared by devShells + home-manager
├── tools/ # non-Nix tool content
| ├── agents/ # agent-instruction overlay (AGENTS.md, ...)
| ├── cheat/ # cheatsheets + cheatpath config
| ├── claude/ # Claude rules
| └── helix/ # helix config
├── secrets/ # agenix/ragenix encrypted secrets
├── docker/ # container notes (per-arch CLAUDE.md) + entrypoint
├── Dockerfile # multi-arch (x86_64 + arm64) dev image
├── .devcontainer.json # Nix Package Manager
├── .gitignore # Nix artifacts
├── flake.lock
├── flake.nix # Home Manager + darwin config
├── justfile # Task Runner recipes
└── README.md
Nix is three things:
| Thing | What it is | You might use it to... |
|---|---|---|
| Nix (language) | A purely functional language for defining packages and configurations | Write .nix files |
| Nix (package manager) | A package manager that installs packages in isolation. Like homebrew. | nix profile install nixpkgs#ripgrep |
| NixOS (os) | A Linux distro configured entirely by Nix files | Run a fully reproducible system |
You don't need all three. This repo leverages Home Manager with flakes in a devcontainer (lightweight Debian with Nix Package Manager).
Step 0 is always the same: stage the age personal key from an existing machine, or you get a working-but-anonymous environment (activation warns; no decrypted git identity, no private overlay). Then follow the tree:
flowchart TD
K0["STEP 0 — always: stage the age personal key<br/>~/.age/personal-key.txt from an existing machine<br/>(without it: no git identity, no private overlay)"]
K0 --> Q1{admin rights?}
Q1 -- "yes — fresh machine" --> Q2{nix installed?}
Q2 -- no --> N1["install Nix, multi-user<br/>(issue #29 one-liner)"]
N1 --> Q3{OS?}
Q2 -- yes --> Q3
Q3 -- macOS --> W1["⚠ ditto only: gh auth BEFORE the switch —<br/>brew bundle clones a private tap over https<br/>mid-activation"]
W1 --> D1["darwin-rebuild switch --flake .#ditto"]
Q3 -- Linux --> D2["home-manager switch --flake .#alyssa@work-dev"]
Q1 -- "no — new user on a set-up machine" --> Q4{"docker app installed globally?<br/>(OrbStack / Docker Desktop, admin's brew)"}
Q4 -- yes --> C1["open -a OrbStack<br/>(daemon + CLI context for THIS user)"]
C1 --> C2["curl -fsSL https://raw.githubusercontent.com/<br/>alycda/dotfiles/main/docker/dev.sh | sh -s -- up"]
C1 -.-> C3["alt: git clone https + VS Code devcontainer<br/>(needs the same daemon)"]
Q4 -- no --> Q5{"/nix exists? (global daemon)"}
Q5 -- yes --> Q6{"in nix-users group?<br/>(daemon socket is group-locked)"}
Q6 -- no --> A1["admin, once:<br/>sudo dseditgroup -o edit -a USER -t user nix-users"]
A1 --> Q6
Q6 -- yes --> H1["home-manager switch --flake .#code<br/>⚠ blocked today: profile hardcodes user 'code'"]
Q5 -- no --> A2[ask admin: install OrbStack or Nix]
D1 --> S1["STEP 1 — always: just _login<br/>(gh auth login --web + claude login)<br/>per-device OAuth, by design — see note below"]
D2 --> S1
C2 --> S1
H1 --> S1
Step 1 is always just _login (gh + Claude, one recipe): each machine
mints its own per-device OAuth tokens, revocable individually.
Why no encrypted PAT? agenix could carry a GitHub PAT (
ghhonorsGH_TOKEN;gh auth login --with-tokenreads one), which would make gh work with zero ceremony the moment the age key is staged. I'm aware of that path and chose against it: one token shared across machines means shared blast radius and revoke-everywhere semantics, and fine-grained PAT expiry turns the one-time per-machine login into a recurring rotate-and-rekey chore. If you're adapting this repo, that path exists and works — it's just not for me.
You need ONE of these:
| Option | What you need | Good for |
|---|---|---|
| Devcontainer | Docker + VSCode with Remote Containers | Exploring without installing Nix locally |
| GitHub Codespaces | A GitHub account | Exploring in the cloud |
| Local Nix | Nix installed | Already have Nix or want to install it |
| Plain Docker | Just Docker (no VSCode, no Nix, no gh) | Locked-down machines — e.g. a non-admin macOS user |
nix-darwin manages macOS system configuration declaratively, including dock apps, system defaults (defaults write) and Homebrew packages.
Verified end-to-end on a clean tart VM (macOS Tahoe base image), 2026-07-01.
- Install Nix (official multi-user installer)
- Enable flakes — the official installer doesn't:
echo "experimental-features = nix-command flakes" | sudo tee -a /etc/nix/nix.confsudo launchctl kickstart -k system/org.nixos.nix-daemon
- Trust the third-party Homebrew taps (new
brewrequires this; also needed once on existing machines after a brew update):brew trust cirruslabs/cli getditto/build-infragetditto/build-infrais private —brew bundleclones it over https, so authenticate GitHub first (gh auth login+ credential helper) or pre-tap it from an SSH clone.
- Move aside the
/etcfiles nix-darwin wants to own (first activation only):for f in /etc/nix/nix.conf /etc/bashrc /etc/zshrc; do sudo mv "$f" "$f.before-nix-darwin"; done
- Bootstrap
nix-darwin(once) — build from this repo's pinned input rather thannix run nix-darwin(which resolves upstream HEAD via the GitHub API: version skew + rate-limited on shared IPs):nix build .#darwinConfigurations.ditto.systemsudo ./result/sw/bin/darwin-rebuild switch --flake .#ditto
- Rebuild after changes
just _rebuild ditto(the onlydarwinConfigurationisditto;alyssa@*names are Linux/devcontainerhomeConfigurations)
Gotchas seen on a fresh machine: the user the flake hardcodes (
alyssaevans) must exist before switching; the Homebrew prefix must be owned by that user (sudo chown -R alyssaevans /opt/homebrew) because nix-darwin now runsbrewas that user;tailscale-app's pkg installer fails inside VMs (system-extension approval) — expected in CI, fine on hardware.
! does not support nix-darwin
- Clone this repo
gh repo clone alycda/dotfiles
- Open in VSCode
ms-vscode-remote.remote-containers
- Click "Reopen in Container" when prompted
- or CMD+SHIFT+P > Dev Containers
- You now have
nixavailable:nix-envnix profile- nix-shell
- Bootstrap Home Manager (once)
nix run home-manager/master -- switch --flake .#alyssa@dev
- You now have
gh,hx,jjandjustavailable:- github cli
- What is jujutsu?
- rebuild with
justorjust _rebuild
For a machine (or user account) where all you have is docker — e.g. a fresh
non-admin user on a Mac whose Nix install belongs to another account. Docker
fetches the repo itself (BuildKit remote build context over https):
curl -fsSL https://raw.githubusercontent.com/alycda/dotfiles/main/docker/dev.sh | sh -s -- upRun it from whatever directory you want mounted at /work. Rebuilding after
a flake change is the same one-liner again — there's no local checkout to
keep in sync.
docker/dev.sh is the single source of truth for the build and run commands:
the volume set, the working directory and the network mode all live there, and
the just docker-* recipes delegate to it. This README deliberately points at
the script rather than repeating its flags — hand-written copies of that
command have drifted from the real one before (#86). The Dockerfile header
carries the one annotated copy, with the optional extras (ssh-agent
forwarding, the ragenix key) and troubleshooting.
If you do want a local checkout (e.g. to hack on the dotfiles from the host):
git clone https://github.com/alycda/dotfiles && cd dotfiles
./docker/dev.sh build-local && ./docker/dev.sh runThe build bakes the home-manager closure for your CPU into the image (arm64 →
alyssa@dev, amd64 → alyssa@dev-x86) and activation happens at container
start. devhome persists nix/jj/ssh state across --rm; claude-home keeps
Claude Code auth in its own volume so a devhome reset never logs you out.
Authenticate gh and claude once inside; see the Dockerfile header for
ragenix keys, ssh-agent forwarding, and troubleshooting.
nix develop github:alycda/dotfilesornix develop github:alycda/dotfiles#tools-c cheat -limmediately run a command AND EXIT
As long as you have docker or an ephemeral environment in the cloud, you can explore my setup without polluting your system.
| Directory | Tool | Purpose |
|---|---|---|
darwin/ |
nix-darwin | macOS system config (dock, defaults, homebrew) |
home-manager/ |
Home Manager | User packages and dotfiles (cross-platform) |
lib/ |
Nix | core-packages.nix, imported by both devShells and home-manager so ephemeral nix develop and persistent profiles stay consistent |
tools/ |
(plain files) | Non-Nix tool content wired in by modules: agents/ instruction overlay, cheat/ cheatsheets, claude/ rules, helix/ config |
secrets/ |
agenix/ragenix | age-encrypted secrets, split by account: personal/ (git config, private agent overlay), work/ |
docker/ + Dockerfile |
Docker | multi-arch dev image: born for a frozen 2012 MacBook Pro (x86, docker/CLAUDE.md), also the no-Nix bootstrap on Apple Silicon (arm64, docker/CLAUDE-arm64.md) |
This keeps package management declarative and reproducible across environments.
allowed-users = @nix-users
build-users-group = nixbld
extra-experimental-features = nix-command flakes- Zero to Nix
- Nix Pills — Deep dive (dense but thorough)
- home-manager — Manage dotfiles with Nix
- hame-manager Option Search
- nix-darwin
- devcontainers — Container-based dev environments
- Orbstack — Fast Docker alternative for macOS
- gh repo clone