Configuration management for a consistent & resilient dev environment

Shell-based configuration management has kept my dev environment consistent & resilient for years. In this post, you’ll learn how to configure Homebrew, Git, chezmoi, fnm, and other CLI tools, so that you can quickly get through the Forge pre-requisites with fewer problems.

How often do you find yourself rebuilding your developer environment? Would you believe that I rebuild mine monthly? Ten years ago, I picked up the habit from a developer much smarter than me. Every rebuild is a chance to put my configuration management skills to the test and learn something new. Technically, it’s rebuilding one of my development environments; I have multiple machines covering Windows, Linux, and MacOS. I only rebuild one a month which means I can fall back to another machine if I’m in a crunch. With all that rebuilding, I’ve gotten good at getting consistency and resiliency in my own environment. In the long-run, that’s really helpful for getting back to productive after a local disaster, or just being able to isolate and reproduce problems. For those with fewer machines to manage, or just less patience to experiment so frequently, here are a handful of my tips on managing a personal development environment, with a particular eye on building a Forge dev environment. These tips have worked for me, but everyone’s setup is slightly different. You can always reach out in the Atlassian Developer Community to provide feedback or get help when you’re stuck.

Use package managers for consistent install

On MacOS, Homebrew is a must for developers. On Linux, I prefer it over my distro’s package manager. I’m usually working on my own machine, not one shared with many people; hence, Homebrew’s “user install” model makes a lot of sense. While I’m not fond of the “curl to shell” install model, I’m OK starting with that step so that I don’t have to resort to various install methods (including “curl to shell”). For various flavors of Linux, I have found it important to install some required packages first. And for some Linux installs that don’t have a root password, it took a while to discover the CI=1 environment variable.

On Windows, I usually boot up Linux via WSL2 for serious work. Occasionally for using Windows natively, Chocolatey is my choice. With different package managers, I am resigned that not everything I can do with Homebrew will be possible with Chocolatey.

The general flow to setting up a new machine starts with checking my package list on one machine to remember what tools are really useful to me right now. For a while, I tried to keep a bootstrapping script in Git but I found my preferences change too fast. It’s just easier to use a working example than to try to keep perfect consistency. So next, I pick out the packages I want and then bulk install on the CLI. Both Homebrew and Chocolatey are happy to install multiple packages in 1 command. Both also have update/upgrade commands that help me keep my toolchain on latest versions. Or, pin to specific versions, when there are troublesome updates.

Keep shell configuration files in a hosted Git repo for reliability

There are dozens of dotfile management options, including just copying config files into a Git repo and pushing changes every now and then. That said, I find the workflow of chezmoi suits my cross-machine needs well. Except for Windows, I keep my shell configurations consistent so I don’t use any of the fancy chezmoi features like templating. For secrets that I load into my environment variables, like Forge credentials, my shell startup reads from Lastpass CLI. What I appreciate most about chezmoi is how it’s aware of which config files I want to keep, and how easy it is to understand changes going in and out.

For me, keeping the following files help me get back to productivity quickly:

Easier Git access with Git Credential Manager

I used to think SSH was great for Git repo access. It’s secure and convenient, preventing me from having to enter a password every time I clone a repo. But with all this environment rebuilding, I was struggling to find a way to manage SSH keys securely and conveniently. Either I have to securely keep my generated key between rebuilds, or I have to reconfigure GitHub & Bitbucket for every new machine. I tried both Lastpass CLI (it even has a special kind of entry for SSH keys) and a USB memory stick. Then came Bitbucket’s SSH host key rotation and it pushed me to try Git over HTTPS again. At which point, I discovered Git Credential Manager (GCM).

GCM works with MacOS Homebrew but not for Linux. It’s bundled with Git for Windows but I can’t find a satisfying package-managed approach for Windows. Once installed, it does work both in UI and CLI environments. Plus, it integrates with all the right credential stores in the underlying OSes for the convenience of passwordless cloning.

Use language version management for consistent environments

In the last few years, I haven’t met a language without a corresponding version manager. Although some languages conflate the language version management with dependency management, there’s no reason not to use one. For the most part, installation of the right one is easy with either Homebrew or Chocolately.

That said, Node is a peculiar case. While many people know about about nvm, install isn’t quite that simple. For one, you have to use a different nvm-windows for Windows (remember, I like consistency). For another, nvm does not support installation through Homebrew (remember, I don’t like “curl to shell” install). Homebrew still has a package and I’ve disregarded it’s install warnings for a long-time without consequence, until I discovered fnm, the “fast node manager”. I like fast! Here’s why you will too.

fnm is better than nvm

To compare with nvm, fnm claims these features:

The first 2 bullets are well worth the change for getting back to productive using my toolchain package manager: both Homebrew and Chocolatey are supported for installation.

The second 2 bullets can be profound enough to change your workflow. I timed nvm switching versions at nearly 1s. It’s slow because it is written in shell and doing a lot of shell magic. Meanwhile, fnm clocks the same version switch at 5ms, or 200x faster! This means I can use the .node-version file in my project, without feeling like switching projects takes forever (who has seconds to spare on the CLI?).

From zero to Forge

Let’s put it all together. Here’s what it can look like to go from a brand new machine to productive with Forge. The following is for Linux so works on Windows with WSL2. The simpler MacOS path is described in a few comments throughout. If you want to follow for yourself, each $ is a prompt. You can skip every line that starts with #, as that’s just a human-readable comment. Ellipsis (...) means some text is returned by the program, I’ve trimmed it down to enough that you should be able tell that you have been successful.

$ ### Homebrew
$ # On MacOS, Homebrew will install Xcode but does not have pre-install
$ # On Linux see pre-install:
$ # https://docs.brew.sh/Homebrew-on-Linux#requirements
$ # For this RHEL-like distro:
$ sudo dnf groupinstall 'Development Tools'
...
$ # Install Homebrew
$ /bin/bash -c "$(curl -fsSL 
...
==> Next steps:
- Run these two commands in your terminal to add Homebrew to your PATH:
    (echo; echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"') >> /home/ec2-user/.bash_profile
    eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
...
$ # Yes, do the next steps. They will be specific to Linux or MacOS and your shell.
$ # Homebrew will recommend the following. It's a good check to make sure everything is configured and a common package dependency.
$ brew install gcc
...
==> Installing gcc
==> Pouring gcc--13.1.0.x86_64_linux.bottle.tar.gz
==> Creating the GCC specs file: /home/linuxbrew/.linuxbrew/Cellar/gcc/13.1.0/bin/../lib/gc
  /home/linuxbrew/.linuxbrew/Cellar/gcc/13.1.0: 1,668 files, 320.4MB
==> Running `brew cleanup gcc`...
...
$ ### Homebrew ready!

$ ### Git Credential Manager
$ # On MacOS, just brew:
$ # brew tap microsoft/git
$ # brew install --cask git-credential-manager-core
$ # On Linux, we have to fall back to installing dotnet first:
$ # https://github.com/git-ecosystem/git-credential-manager/blob/release/docs/install.md#linux
$ sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
...
$ sudo yum install dotnet-sdk-7.0
...
$ dotnet tool install -g git-credential-manager
...
Since you just installed the .NET SDK, you will need to logout or restart your session before running the tool you installed.
You can invoke the tool using the following command: git-credential-manager
Tool 'git-credential-manager' (version '2.3.1') was successfully installed.
$ # Tell git to use GCM
$ git config --global credential.helper manager
$ # Optionally, on Linux tell GCM how to store credentials
$ export GCM_CREDENTIAL_STORE=cache
$ ### Git Credential Manager ready!

$ ### Chezmoi
$ brew install chezmoi
...
==> Summary
  /home/linuxbrew/.linuxbrew/Cellar/chezmoi/2.36.1: 8 files, 30.9MB
==> Running `brew cleanup chezmoi`...
...
$ # If you have your config files in chezmoi, you can pull config.
$ # chezmoi init --apply https://bitbucket.org/ian_buchanan/dotfiles.git
$ # Otherwise, assuming this is your first time through with chezmoi...
$ chezmoi init
$ # Using the same config file I changed earlier for Homebrew...
$ chezmoi add ~/.bash_profile
$ # ... Repeat for important config files.
$ chezmoi cd
$ git config --global user.name $GIT_USERNAME
$ git config --global user.email $GIT_EMAIL
$ git add .
$ git commit -m "Initial commit"
$ # You can use GitHub or GitLab, if you prefer.
$ git remote add origin https://bitbucket.org/$BITBUCKET_USERNAME/dotfiles.git
$ git branch -M main
$ git push -u origin main
$ ### Chezmoi ready!

$ ### Final stretch to Forge...
$ brew install fnm
$ (echo; echo 'eval "$(fnm env --use-on-cd)"') >> /home/ec2-user/.bash_profile
$ eval "$(fnm env --use-on-cd)"
$ fnm install --lts
Installing Node v18.17.1 (x64)
$ fnm use default
Using Node for alias default
$ npm install -g @forge/cli
...
added 7 packages, and changed 942 packages in 29s
$ forge --version
6.16.1
$ ### Forge ready!

Wrapping up

This approach to shell-based configuration management has kept my dev environment consistent & resilient for years. Now that you’ve learned how to quickly reconfigure your personal development environment, you’ll be able to quickly get through the Forge pre-requisites with fewer problems. I hope you find these tips helpful. Do you have your own tricks to keep your local environment running smoothly? Or, are you still getting stuck in the “getting started” with Forge? Head to the Atlassian Developer Community and join the conversation.

Exit mobile version