Why I split my dotfiles into tiny packages (and stopped breaking other machines)

I moved from one big dotfiles repo to several small, opinionated packages with a tiny bootstrap. Fewer surprises, safer installs, and one painful mistake that forced the change.

Written by: Arjun Malhotra

A laptop on a wooden desk with a terminal open and a cup of coffee beside it
Photo by Dan Nelson on Unsplash

It was 11 pm. I was helping a friend set up their new work laptop over a shaky Vodafone connection in a Bengaluru apartment. Two commands and a reboot later, their shell prompt looked right, vim had my plugins, and their keyboard layout had quietly changed to Dvorak. They texted a thumbs-up. Then their package manager started failing. Then the touchpad stopped recognising taps. Then they called.

I had done this hundreds of times on my own machines: clone my single large dotfiles repo, run install.sh, and everything fell into place. It was fast. It felt elegant. It also assumed too much: my preferences, my distro quirks, my global installs. My “one script to rule them all” model kept leaking — replacing defaults, symlinking config files across /etc, or running scripts that expected Homebrew on macOS and apt on Ubuntu. On someone else’s machine it behaved like a mildly malicious configuration virus.

That night ended with me reverting commits, restoring backups, and promising I’d never again make “one install” an all-or-nothing gamble for other people’s laptops. That promise became the reason I split my dotfiles into small, purpose-first packages and a tiny bootstrap that asks questions.

Small packages, big difference

My new model is simple: each logical piece of my setup is its own repo (or folder in a monorepo) with a single responsibility and clear, idempotent install steps.

Examples:

The intent is that “install shell” should be safe on any machine. “install laptop/ubuntu” is explicitly for my personal laptop and begins with a confirmation: “This will change system settings and install packages. Proceed? [y/N]”.

Why this works for me in India

How I bootstrap now

I carry one tiny install script in a single repo: bootstrap.sh. Its job is not to configure everything but to ask, clone, and delegate.

Sequence:

  1. clone bootstrap
  2. run ./bootstrap.sh
  3. script lists available packages with one-line descriptions and shows platform compatibility
  4. I pick which packages to install (or pass flags)
  5. each package’s install script runs in a sandboxed way: it writes to $HOME only, creates backups (foo.conf.orig.YYYYMMDD), and logs actions to ~/.local/dotfiles/logs

This sounds fussy. It is. But the friction is intentional: I want the person running it to think twice before allowing something to touch system files.

The failure that shaped the rules

After I switched to packages, I made a rookie mistake: a package I wrote to “improve” keyboard shortcuts on my Ubuntu laptop invoked gsettings without checking whether gsettings was present. On a friend’s minimal Ubuntu install, that call failed halfway, leaving their gnome-settings messed up. I thought my safety checks were sufficient. They weren’t.

I fixed it by hardening installs:

Tradeoffs I accepted

Practical rules I enforce now

What really changed

The first week I switched, I felt slower. Then I noticed fewer “what did you run on my machine?” messages. I could hand a coworker a link to “shell/” and they could install only that part. New hires stopped asking me to “set up my laptop” — I gave them the selective link and added a checklist to our onboarding doc.

If you care about reproducibility, this approach is not exotic: it’s modular design applied to personal configs. The real win for me was social: safer defaults mean fewer late-night debugging sessions for other people and less cognitive load when I switch machines.

One takeaway (and a question)

If you find yourself running install.sh on other people’s work laptops, split your repo. Make installs idempotent, ask before changing system settings, and keep packages small. It buys you safety and fewer angry late-night calls.

One question I still wrestle with: how opinionated should a “personal” package be when it’s meant for others? My current rule is simple — if it would annoy you being applied silently to someone else’s laptop, it should be opt-in. That one rule saved me more hours than all my automation combined.