Guide · Foundations

Set up your dev machine like a pro

Your machine is the tool you touch most. Set it up deliberately — organised, reproducible, and secure — and it pays you back every single day. Here's the checklist we give everyone joining the team.

1 · Why a deliberate setup matters

A messy machine is a slow, risky machine. Files you can't find. Ten versions of Node installed three different ways. Apps that silently went out of date months ago. Passwords saved in a browser you no longer trust. None of it screams "problem" on day one — it just quietly taxes you forever.

A good setup optimises three things at once:

One principle ties it all together. Prefer tools that manage themselves. A package manager that updates every app, a password manager that generates every password, an antivirus that updates its own definitions — automation beats willpower.

2 · A home for everything: file organisation

The single best habit: give your code one root folder and never scatter projects across the Desktop and Downloads. On macOS, ~/Development (or ~/Developer) is the convention.

# One root for all your work — create it once
mkdir -p ~/Development

Inside it, one folder per project, each self-contained. Consistency is the point: when every project looks the same, you never have to think about where anything lives.

~/Development/
├── mindmerge/           # one folder = one project = one git repo
│   ├── README.md        # what it is + how to run it
│   ├── .gitignore       # what git should ignore (incl. secrets)
│   ├── .env             # local secrets — NEVER committed
│   └── src/
├── client-website/
└── experiments/         # throwaway/learning projects live together

Bonus habit: dotfiles. Your config files (shell, git, editor) can themselves live in a git repo called dotfiles. That way your whole setup — aliases, git identity, editor settings — is version-controlled and portable to any new machine.

3 · Install through a package manager, not one-off downloads

Here's the habit that separates a professional setup from a fragile one. When you need a tool — a language, a database, an app — don't hunt down a website and double-click an installer. Install it through a package manager.

What's wrong with manual, "isolated" installs? Each one becomes an island you have to maintain by hand:

Manual downloadPackage manager
You must remember to check each app for updates.One command updates everything at once.
Versions drift — "works on my machine" bugs.Everyone can install the same versions.
You trust whatever random site you landed on.Packages come from a curated, checksummed catalog.
No record of what you installed.Your whole setup is a list you can replay.

On macOS the standard is Homebrew (brew). Install it once with the official one-liner from brew.sh:

# Install Homebrew (the official installer from brew.sh)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Homebrew installs two kinds of things:

# Command-line tools & languages (formulae)
brew install git node gh

# Desktop apps (casks) — installed & updatable just like CLI tools
brew install --cask visual-studio-code google-chrome iterm2

# Search before you install
brew search postgresql

For apps that only live in the Mac App Store, add mas so even those are scriptable:

brew install mas
mas search Xcode
mas install 497799835        # install by its App Store ID

Not on a Mac? Same idea, different tool: Windowswinget (built in) or Chocolatey (choco); Linux → your distro's manager (apt, dnf, pacman). The habit is universal: install through the manager, not the browser.

4 · Make it runnable: your PATH and shell profile

You install a tool, type its name, and the terminal snaps back command not found. Nine times out of ten the tool is installed perfectly fine — your shell just doesn't know where to look. This one idea trips up nearly everyone, so spend ten minutes on it now and never lose an hour to it later.

When you run a command, your shell walks through a list of folders called $PATH and launches the first matching program it finds. If the tool's folder isn't on that list, you get command not found — even though the file is sitting right there on disk.

Installed ≠ findable. Reinstalling almost never fixes command not found — you just install the same thing to the same place your shell still isn't looking. Fix the PATH, not the install.

So when a command isn't found, run this three-step triage instead of reaching for the installer again:

# 1 · Is it actually installed? Find the binary.
brew --prefix the-tool        # where Homebrew put it, if it's a brew tool
ls ~/.the-tool/bin             # non-brew installers often drop binaries here

# 2 · Is that folder on your PATH?
command -v the-tool             # prints the path if found — nothing if not
echo "$PATH" | tr ':' '\n'     # list every PATH folder, one per line

# 3 · Missing? Add the folder to PATH (below), then reload your shell.

Homebrew's "keg-only" trap

Most Homebrew tools land in /opt/homebrew/bin, which is already on your PATH — so they just work. But a few are deliberately not linked, so they don't shadow a version macOS itself relies on. Homebrew calls these keg-only, and Java (openjdk) is the one nearly everyone hits: brew installs it, yet java stays "not found" until you wire it up. Conveniently, brew prints the exact lines you need:

# Ask brew how to wire up a keg-only formula — it prints the exact lines
brew info openjdk

# For Java: point JAVA_HOME at the keg and put its bin on PATH
export JAVA_HOME="$(brew --prefix openjdk)"
export PATH="$JAVA_HOME/bin:$PATH"

Set both: many JVM tools (Maven, Gradle, and SDKs that themselves run on Java) look for JAVA_HOME, not just java on the PATH. And if you want every app — including /usr/bin/java and GUI tools — to find it, register the keg with the system once (this one needs your password):

# Optional: let macOS's own java_home discover the keg, system-wide
sudo ln -sfn "$(brew --prefix openjdk)/libexec/openjdk.jdk" \
  /Library/Java/JavaVirtualMachines/openjdk.jdk

Tools installed outside Homebrew

Not everything comes from a package manager — a security suite, some vendor SDKs and language toolchains ship their own installer. Those usually drop the program in a hidden folder like ~/.the-tool/bin and try to add it to your PATH by editing a shell file for you. Two things routinely go wrong: the installer edits the wrong file, or you simply never reloaded your shell. Which brings us to the real gotcha…

Which profile? .zshrc vs .bash_profile

Your PATH lines only take effect if they live in the file your shell actually reads at startup — and you probably have more than one such file lying around. Check which shell you run, then edit its file:

echo "$SHELL"      # /bin/zsh, /opt/homebrew/bin/bash, …
Your shellStartup file it reads (macOS)
zsh — the macOS default~/.zshrc
bash — in Terminal (a login shell)~/.bash_profile  (not ~/.bashrc)

This is the subtle one: macOS Terminal opens a login shell, and there bash reads ~/.bash_profile — so a PATH line an installer wrote into ~/.bashrc silently never runs. Put your exports in the right file, and have .bash_profile pull in .bashrc so there's just one place to maintain:

# Add a tool's folder to PATH permanently (bash in Terminal)
echo 'export PATH="$HOME/.the-tool/bin:$PATH"' >> ~/.bash_profile

# Make bash also load .bashrc, so you maintain a single file
echo '[ -r ~/.bashrc ] && . ~/.bashrc' >> ~/.bash_profile

# Apply it right now — no need to quit the terminal
source ~/.bash_profile

A good habit is to keep all of this in one clearly-commented block in your profile, so future-you knows why each line is there. A sensible starting point:

# ~/.bash_profile — keep your shell environment in one place

# Homebrew keg-only tools (Java, etc.) — point tools at them explicitly
export JAVA_HOME="$(brew --prefix openjdk)"
export PATH="$JAVA_HOME/bin:$PATH"

# Tools installed outside Homebrew add their own bin folder
export PATH="$HOME/.the-tool/bin:$PATH"

# Load .bashrc too, so aliases & prompt live in a single file
[ -r ~/.bashrc ] && . ~/.bashrc

Added it and it's STILL not found? You almost certainly didn't reload. A shell reads its profile once, at startup — so either run source ~/.bash_profile or open a fresh terminal window before you try again.

5 · A reproducible machine (Brewfile)

Because everything is installed through Homebrew, your entire setup can be captured in a single text file — a Brewfile — and replayed on any Mac. This is the same "recipe you can rebuild from" idea as a project's lockfile, but for your whole machine.

# Brewfile — a portable description of your machine
tap "homebrew/bundle"

# CLI tools
brew "git"
brew "node"
brew "gh"
brew "mas"

# GUI apps
cask "visual-studio-code"
cask "google-chrome"
cask "bitwarden"
cask "iterm2"
# Save your current setup to a Brewfile...
brew bundle dump

# ...and reinstall everything on a fresh machine from it
brew bundle install

Keep the Brewfile in your dotfiles repo. New laptop day becomes: install Homebrew → brew bundle install → coffee. Everything you use, back in minutes.

6 · Updates are security

Most successful attacks don't use exotic zero-days — they exploit known holes in software people never got around to updating. Every update you skip leaves a documented, publicly-known door open. Staying current is the highest-value security habit there is, and the package manager makes it a one-liner.

# Update EVERYTHING Homebrew manages, in one go
brew update      # refresh the catalog of available versions
brew upgrade     # upgrade all installed formulae & casks
brew cleanup     # remove old, superseded versions

Some apps update themselves and are skipped by a normal upgrade; sweep those in too with --greedy:

brew upgrade --greedy   # also update casks that self-update

Your operating system is the most important thing to keep patched. On macOS, turn on automatic updates (System Settings → General → Software Update → Automatic Updates), and you can also check from the terminal:

softwareupdate --list            # see what's available
softwareupdate --install --all    # install all pending OS updates

Update runtimes and frameworks through the manager too — never by re-downloading an installer over the top. Update Node with brew upgrade node (or a version manager like nvm/fnm if you juggle versions); update project libraries with your language's manager (npm, pip, …) and commit the updated lockfile.

Make it a rhythm, not a heroic act. Run the update trio once a week (or automate it with a tool like brew autoupdate). "I'll do it later" is how machines end up years behind.

7 · Passwords: use Bitwarden, not your browser

A password manager creates a long, random, unique password for every account and remembers them all behind one master password. This is non-negotiable for anyone with access to company systems — password reuse is how one leaked site turns into a dozen compromised accounts.

Turn off your browser's built-in password manager

Browsers offer to "save passwords," and it's convenient — but it's the wrong tool for the job. Turn it off and use a dedicated manager instead. Why:

Disable it (do this in every browser you use):

BrowserWhere to turn it off
Chrome / EdgeSettings → Autofill and passwords → Password Manager → turn off Offer to save passwords & Auto Sign-in
SafariSettings → AutoFill → uncheck User names and passwords
FirefoxSettings → Privacy & Security → Logins and Passwords → uncheck Ask to save logins

Install Bitwarden — app and browser extension

Our pick is Bitwarden: open-source, audited, free for personal use, and it runs everywhere. Install both pieces — the desktop app (for a system-wide vault) and the browser extension (for autofill on websites):

# Desktop app via Homebrew (updates with the rest of your machine)
brew install --cask bitwarden

Then set it up right:

Your master password can't be recovered. Bitwarden encrypts your vault with it and never sees it — if you lose it, no one can reset it for you. Memorise it, and store an emergency copy somewhere genuinely safe (a sealed note at home, not a file called passwords.txt).

8 · Antivirus & keeping it current (Bitdefender)

"Macs don't get viruses" is a myth — malware, adware and phishing payloads target every platform, and a work machine is a valuable target. Run reputable endpoint protection as one layer of defence in depth. Our pick is Bitdefender.

This is the one deliberate exception to "install everything through Homebrew." Full security suites are distributed through the vendor's own installer with a built-in updater — and that's correct: you want the security vendor pushing new threat definitions directly, several times a day.

Antivirus is a layer, not a force field. It complements — never replaces — the basics: stay patched, use a password manager, enable 2FA, and think before you click a link or run a script you didn't write.

9 · While you're here: quick security wins

Three more things that take five minutes and dramatically raise your security floor:

10 · Day-one checklist

Run through this on any new machine and you're set up like a professional:

FilesCreate ~/Development; one folder per project; secrets in a git-ignored .env.
Package managerInstall Homebrew; install tools & apps via brew/brew --cask; capture a Brewfile.
PATH & shellKnow your shell (echo $SHELL); put PATH exports in the file it reads; source or reopen to apply.
UpdatesWeekly brew update && brew upgrade && brew cleanup; OS auto-updates ON.
PasswordsDisable browser password saving; install Bitwarden app + extension; strong master password; 2FA on.
AntivirusInstall Bitdefender from Central; auto-update & real-time protection ON.
HardeningFileVault ON, firewall ON, Time Machine backups running.

Do this once, keep the good habits, and your machine stays fast, reproducible and safe — so you can spend your attention on the actual work. Next, if you haven't yet: read the Git & GitHub guide and make your first pull request. 🚀

← Back to MindMerge Back to top ↑