How to set up a new machine for development.

•

•

Set Up the Laptop in One Command

I spent three days once trying to follow a “perfect” DevOps tutorial for a new laptop, only to end up with a broken PATH variable and a deep sense of resentment. Most guides on how to set up a new machine for development are written by people who have never actually had to debug a production incident while their local environment is throwing arbitrary permission errors. They focus on the shiny new tools or the most complex container orchestration, but they completely ignore the friction that happens when you’re just trying to run a simple test suite and your version of Python decides to go rogue.

I’m not here to sell you on a specific ecosystem or a suite of overpriced productivity tools. Instead, I want to show you the practical, slightly boring way to build a workstation that actually stays out of your way. I’ll be sharing the specific configurations and automation scripts I’ve refined over fifteen years of getting things wrong so that you don’t have to. My goal is to help you build a setup that is predictable, reproducible, and—most importantly—doesn’t require a complete reformat the next time a dependency update breaks your entire world.

Table of Contents

Automated Environment Provisioning to Stop Manual Errors

Automated Environment Provisioning to Stop Manual Errors

I used to think I was being efficient by manually installing my tools—`brew install` this, `npm install` that—until I had to migrate to a new MacBook and realized I had no idea which specific version of a library was keeping my local database from spinning up. That’s when I stopped treating my laptop like a pet and started treating it like a disposable instance. Moving toward automated environment provisioning isn’t about being a DevOps purist; it’s about making sure your machine is a predictable environment rather than a collection of “magic” configurations that only work because you remember a specific command you ran six months ago.

I eventually settled on a setup using a mix of Homebrew bundles and a few shell scripts to handle my developer workstation configuration. I don’t go overboard with complex tools, but I do keep my setup documented in a single repository. If I can’t rebuild my entire workspace from a single command, I haven’t actually finished setting it up. The goal is to minimize the cognitive load of the “new machine” phase so you can actually get to the code, rather than spending three days debugging why your PATH is slightly off or why a certain binary is missing.

Infrastructure as Code for Developers Who Hate Repeating Mistakes

Infrastructure as Code for Developers Who Hate Repeating Mistakes

I used to think that manually installing my tools was a fine way to spend an afternoon, but that was before I lost a full day of work because I couldn’t remember which specific version of a library I’d used for a side project six months prior. Now, I treat my developer workstation configuration like any other production service. If I can’t recreate my entire environment from a single command, I don’t trust it. I’ve moved away from “remembering” steps and toward using actual infrastructure as code for developers—specifically, keeping my dotfiles and tool manifests in a dedicated Git repo.

This isn’t about being a perfectionist; it’s about survival. When you use something like Ansible or even just a well-structured set of shell scripts, you aren’t just installing software; you are documenting your own workflow. I’ve found that the real value isn’t in the speed of the initial setup, but in the ability to audit exactly what changed when a tool starts behaving strangely. By treating my local machine as a reproducible unit, I stop treating my setup as a collection of “vibes” and start treating it as a predictable piece of engineering. It’s a bit of extra work upfront, but it’s much cheaper than the alternative.

Five things I learned from losing my configuration to a laptop theft

  • Treat your dotfiles like source code. I used to keep my Zsh aliases and Vim configs in a random folder in my home directory, which was a disaster. Now, they live in a dedicated Git repo. If my machine dies, I should be able to run one command and have my terminal look exactly how I like it again.
  • Use a version manager for everything. Installing Node or Python directly via Homebrew or a system package manager is a trap. Eventually, a project will require an older version, and you’ll spend three hours debugging why your global environment is fighting your local dependencies. Use `asdf` or `nvm`; pick one and stick to it.
  • Keep your “heavy” tools in containers. I’ve learned the hard way that installing Postgres, Redis, and RabbitMQ directly on my macOS is a recipe for a slow, cluttered system. Use Docker for the services. It keeps the host machine clean and, more importantly, ensures that “it works on my machine” actually means something when you push to staging.
  • Document the “why,” not just the “how.” When I add a weird environment variable or a specific SSH config hack, I leave a comment in my setup script explaining why it’s there. Six months from now, I won’t remember if that flag was a requirement for our legacy payment gateway or just a mistake I made during a late-night debugging session.
  • Automate the boring stuff with a single bootstrap script. Setting up a new machine shouldn’t be a two-day ritual of clicking “Next” on installers. I have a shell script that handles the basics—Homebrew, my core CLI tools, and my shell setup. It’s not perfect, but it gets me from a blank terminal to a working environment in twenty minutes instead of twenty hours.

The things I actually care about

Automation isn’t about being fancy or following a trend; it’s about ensuring that when you inevitably have to rebuild your machine after a catastrophic failure, you aren’t spending three days manually installing the wrong version of a library.

Treat your local environment configuration like production code—version it, document the “why” behind the quirks, and assume that if it isn’t in a script, it doesn’t exist.

The goal isn’t a perfect, sterile setup, but a predictable one where the trade-offs are known, so you spend your mental energy solving actual business logic instead of fighting your own terminal.

The Long View on Local Environments

The Long View on Local Environments.

At the end of the day, setting up a new machine isn’t about achieving some state of perfect, sterile purity. It’s about reducing the friction between having an idea and actually seeing it run in a terminal. By moving toward automated provisioning and treating your local configuration like the infrastructure it actually is, you stop wasting your cognitive load on trivialities like PATH variables or missing dependencies. You aren’t just installing software; you are building a repeatable system that ensures your environment remains a tool rather than a source of unpredictable downtime when you’re trying to push a critical fix.

I’ve spent far too many hours debugging my own machine instead of the actual codebase, and I don’t plan on doing it again. My goal with these setups isn’t to follow a trend, but to build a safety net for my future self. If you take the time to document and automate these steps now, you’ll thank yourself six months from now when you’re staring at a fresh laptop and realize you can be productive in twenty minutes instead of two days. Build it so that the setup is a non-event, so you can get back to the part that actually matters: solving the problem.

Frequently Asked Questions

If I go all-in on Infrastructure as Code for my local machine, am I just adding another layer of complexity that will break when I actually need to get work done?

Yes, you are. If you try to automate every single dotfile and niche CLI tool via Terraform or heavy Ansible playbooks, you’ll spend more time debugging your environment than writing feature code.

How much of my "golden setup" should I actually automate versus just keeping a simple, readable list of commands in a README?

If it takes more than ten minutes to run, automate it. If it takes thirty seconds, just put it in a README. I’ve learned the hard way that over-engineering your setup is just another way to create a maintenance burden you’ll eventually ignore. Automate the heavy lifting—the toolchains, the binaries, the environment variables—but keep the high-level logic in a simple script or a list. You want a repeatable process, not a black box that breaks when a dependency updates.

At what point does a customized dev environment become a liability for the rest of the team?

It becomes a liability the moment you can’t explain your setup to a junior dev without a thirty-minute lecture. If your “optimized” environment relies on a specific version of a tool that you installed via a one-off shell script three years ago, you haven’t built a workflow; you’ve built a debt trap. If it isn’t reproducible via the same IaC we just discussed, it’s not an asset—it’s a single point of failure waiting to happen.

How do I handle the inevitable drift when I inevitably make a "quick fix" to my local config and forget to commit it to my provisioning scripts?

The “quick fix” is a trap. You change a local environment variable or tweak a config file to bypass a bug, it works, and you move on. Three months later, the provisioning script fails on a fresh machine because that one line is missing.

Tomasz Wierzbicki

About Tomasz Wierzbicki

I am not interested in arguing about frameworks. I am interested in why the thing broke at two in the morning and what would have prevented it. Most of what I know came from being wrong in production and writing it down afterwards.