Matthew Lymer
Senior Software Engineer
Making developer onboarding easy
Engineers should have fun on day one of a new job, not spend hours struggling to get their development environment set up.
In an ideal world, developer onboarding would be painless. Sadly, I’ve had my share of unpleasant onboarding experiences. In the past, I’ve worked at places where it has taken days to configure my development environment. This included following out-of-date onboarding documentation, restoring backups to an unsupported database version, and waiting a week for IT to get back from vacation in order to get the necessary permissions.
At Fora, I’ve contributed to a number of different projects. On each one, though, in order to get my first pull-request out the door, I needed access to various tools and repositories to configure my development environment, and to know how to perform typical computing tasks, such as attaching a debugger and running unit tests.
Thankfully, the onboarding process at Fora is fairly pleasant. For every project I join, I automate the machine provisioning process to provide the best bang for my buck in terms of effort.
Here’s how you can do it, too.
Automate machine provisioning
Since much of the software at Fora is written using an array of languages and tools, it’s really helpful if setting up the developer environment is as smooth as possible. This is probably the most time-consuming part of onboarding, so automation is extremely valuable here.
We have a collection of shell scripts to make provisioning your environment a breeze. I’ve recently been writing Ansible playbooks to promote maintainability with clear and verbose output. The aim is to give developers an awesome out-of-the-box experience. Of course, developers can also configure their machine to whatever suits them.
When writing these automation scripts, we found the following guidelines to be particularly helpful:
Make them idempotent
Like the software itself, dependencies will change over time. Assume that the script will be run frequently and dependencies may be installed, upgraded, uninstalled, or otherwise modified.
Don’t overwrite existing installations
Different projects typically require different tools, but sometimes they use the same tools, albeit different versions. To avoid conflicts, we use versioning tools wherever possible, such as nvm for Node, gvm for Go, virtual environments for Python, and Docker.
Be aware of developers’ hardware
Developers may be assigned different hardware, so you should ensure your script works on different architectures and platforms. Use command uname -m in automation scripts to determine the machine's architecture.
if test "$(uname -m)" = "arm64"; then
# install arm dependency
fi
This is particularly useful when installing software via tarballs or other software delivered by package managers.
Example
The following script will install Visual Studio Code, with an extension, and Node v16:
#!/bin/zsh
set -e
source ~/.zshrc &> /dev/null || true
# install vscode
command -v code &> /dev/null && echo Visual Studio Code already installed, skipping. || brew install visual-studio-code
# install vscode extensions
stdout=$(code --install-extension DavidAnson.vscode-markdownlint)
echo $stdout | grep -q 'was successfully installed' && $stdout || echo markdownlint already installed, skipping.
# install nvm and configure shell
if ! command -v nvm &> /dev/null; then
brew install nvm
if ! grep -q 'GENERATED: configure nvm' ~/.zshrc; then
echo >> ~/.zshrc
echo "# GENERATED: configure nvm" >> ~/.zshrc
echo 'export NVM_DIR="$HOME/.nvm"' >> ~/.zshrc
echo '[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh"' >> ~/.zshrc
echo '[ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm"' >> ~/.zshrc
echo >> ~/.zshrc
source ~/.zshrc
fi
else
echo nvm already installed, skipping.
fi
# install node 16
nvm list 16 &> /dev/null && echo Node v16 already installed, skipping. || nvm install 16 --lts
Script frequently performed tasks
As many of our projects use different tooling, it’s important to have a clear entrypoint for running the application, executing tests, and profiling the code – whether this is via the command line or within an IDE.
When using the command line, we may use a task runner so the developer doesn’t have to issue a series of commands. We use environment-specific task runners like npm when possible. I’m personally a fan of leveraging make, as it is ubiquitous on unix-like platforms, and the syntax is straightforward.
Example
Here’s a sample Makefile which will execute python unit tests, profile the application, and run the application in watch mode:
.PHONY: test
test:
python \
-m pytest \
-v \
-W 'ignore::DeprecationWarning:aiomcache.pool'
.PHONY: profile
profile:
@echo Profiler visualization will open when the application exits.
PROFILER_OUTPUT=profiling/output.pstats uvicorn main:app
snakeviz profiling/output.pstats
.PHONY: watch
watch:
uvicorn main:app \
--reload \
--reload-include resources/*.gql \
--use-colors
The usage is simple:
make watch
Use Single Sign On (SSO)
Fora uses a lot of third-party services to handle ancillary concerns, such as human resources software, project management, documentation, source control, incident management, and team communications.
The services are provided by many vendors who would typically require their own login credentials. Instead of requiring new developers to create credentials and remember them for many different sites, we use Single Sign On to remove this concern entirely.
For software that is self-hosted and doesn't already support SSO, an OAuth2 proxy adds this useful capability. This is preferable to requiring the use of a VPN as it eliminates the need for contributors to configure yet another dependency while also removing a point of failure.
Conclusion
At Fora, we’re continually updating our onboarding process. I can see a future for onboarding where we reduce the number of steps even further by incorporating technologies like dev containers or cloud-hosted development environments – which also have the benefit of more collaboration options. More importantly, new hires are a great source of new ideas on how to make onboarding even better. We incorporate their feedback, learn from solutions they’ve used before, and benefit from their expertise to make the developer experience even better.