How I Engineered My Personalized Development Environment And How You Can Too
(Note: the code snippets do not look great in this article. I’m publishing for now because I want to get the content out. I plan to adjust the styling later)
In Figure 1 below, you will see a photo of a “mobile” thin-client version of my personalized development environment. Look closely. You will see it is running on an iPad. I have found the iPad to be pretty good for on-the-go development because a) it is light b) has long battery life c) has a great screen d) is cheaper than a typical developer-spec laptop and e) there are no secrets or code are stored locally presenting less of a security headache if the device is lost or stolen.
Look even more closely. There are no compromises made (I do however leave my beloved, big and heavy Unicomp New Model M keyboard behind). You will see I’m running my preferred toolset including tmux, Neovim and zsh tripped out with starship. The colors look beautiful. The fonts are pleasing to the eye. In a thin-client model, the bulk of the heavy lifting is done on a server somewhere - tmux isn’t really running native on my iPad.

Figure 1 — Neovim and tmux.. On an iPad.. What in the world?
In this particular scenario, I’m developing my blog using Hugo. I therefore need to be able to securely see the results of my code changes in a browser on the client. In Figure 2 below, you will see that this indeed possible:

Figure 2 — Accessing remote ports safely - how?
Although not obvious from the photo, this development environment workspace was automatically provisioned from a template in a matter of minutes customized with my own dot-files. It is also trivial to share the template with my team (ok, I’m currently a team of one - but you get the idea). How is this even possible?
In contrast, here is my “static” thick-client version of my personalized development environment. I develop on both Linux and Mac. Can you tell the difference? Notice how consistent the developer experience is across all devices. This level of consistency I have found reduces friction and enables me to be more productive whether I’m working at home on either Linux or Mac, or on the move using my iPad.

Figure 2 — Linux and Mac - but they look the same?
In this article, I will share the process and the tools I used to achieve this outcome.
Spoiler alert: I am using Coder self hosted on my own server in my home lab. I am using Twingate to provide secure access back to home lab for when I travel. I’m using blink.sh on the iPad as my terminal emulator + SSH client.
Crafting The Development Environment
This is the slowest stage. The best tools are the ones you are most productive with. If you have been stuck using the same tools for the past few years, you may be missing out. There are some incredible tools available whether you prefer an off-the-shelf IDE such as VS Code or IntelliJ, or prefer something like Emacs, Neovim or Zed.
When I was working at Cisco as a Director of Engineering, I didn’t really have the time to craft my own decent development environment and found VS Code (with Vim Motions of course) fit perfectly my casual needs. However, while being on sabbatical I’ve had the luxury of time to invest in crafting my own personalized development environment that fits me like a glove.
These video courses from typecraft provided me with the motivation to get started: Neovim for Newbs and Tmux for Newbs. In addition, I also found inspiration from this video by Josean Martinez.
Now might be a good time for you to learn a little about Coder. The best way I think is to watch @typecraft_dev’s wonderful YouTube video.
Once you have crafted what works for you, take note of what you installed and how you installed them. You will need this information to build a template using Coder. If you are working as part of a team, it is important to enable each developer to have control over their personalized development environment. What works for you may not work for everyone else. Find out what is common across the entire team in terms of tools, and automate that in such a way that you leave the door open for them to individually customize to their unique needs. 80% automation is better than 0% automation. However, for my team of just me, myself and I, a template that fully automates 100% of the development environment is what I need.
Automate
To fully automate the provisioning of my development environment requires the use of Docker, Terraform and Ansible. Coder has lots of starter templates available. I found the Docker template meets my needs. There are other ways supported by Coder so find the way that works for you. It helps to know a little bit about Docker, but it’s not essential. I also needed to tweak the Terraform script provided by the template. I hadn’t used Terraform before but it was pretty straightforward to inject my own custom code within the template. I chose Ansible as the final step to apply the finishing touches to my development environment. You should expect this stage to be highly iterative. You will most likely not get to your perfect template in one pass. I went through many iterations to finally get to my completed template.
In Figure 3, you can see the various packages that form the ingredients of my development environment. There is some unavoidable complexity here. It would be wonderful if there was a single package manager to rule them all. Maybe one day there could be but not today.

Figure 3 - The packages that form the ingredients of my development environment
Docker
It all begins with building a solid base. Using Docker, I use a base image from Ubunutu. Use whatever base image works for you. The smaller the better to reduce the maintenance burden of vulnerability management. The only changes I made to the starter template was to add the apt packages I needed and remove the ones I don’t need. I don’t explicitly need all of these packages, but implicitly I do. For example, build-essential is required by my Neovim LSP configuration (GNU Make specifically). I also rely on GNU Stow to manage the dot-files. Finally, pipx is needed so that I can install Ansible. The other packages should be fairly self-explanatory.
For reference, here is my Dockerfile:
|
|
Terraform
Once a Docker container has been created, Terraform is used to further customize it. Much of the Terraform script in the template can be safely ignored and left in-tact. I just need to make a few additions which are highlighted in lines 13-36 from this snippet of the main.tf file.
It should be fairly self-explanatory what is going on here:
|
|
The final step (line 34) is where I run my Ansible playbook.
Ansible
Here is my Ansible playbook. It’s the final stage of provisioning. What I like about using Ansible (versus a hand-coded shell script) is the declarative nature of it. I also like the idempotent property of the playbook which means I can run it over and over again and skips tasks that have previously been performed.
|
|
Building a Workspace
Now that we have a template, we can create a workspace from it as follows:


Watch the wheels turn:

It doesn’t take long to create the workspace. Now, let’s connect to it using the browser-based terminal by clicking on the “Terminal” button:

Here it is. It mostly looks pretty great but notice the fonts don’t look quite right. The browser based terminal can work for you in a pinch, but I prefer to use a decent native terminal emulator.

For the iPad, I use blink.sh. For Linux and Mac, I use Alacritty. In the screenshot below, I’m using iTerm2 on the Mac. You will also need to install the Coder client to enable you to SSH into your workspace. This is not available on the iPad, so the way I get around that is to “jump” via SSH onto another machine on my network that does have the Coder client installed. Notice that I need to quickly configure the SSH client before I can SSH into my workspace:

Everything looks great. Let’s run tmux Neovim to make sure they are fully configured:

Using the Workspace
The only thing missing are clones of my repos so that I can get to work. Before I can clone the repos, an SSH key needs to be generated and registered in my GitHub profile. Fortunately, I have a script that does that. Here it is:
|
|
Let’s run it:

Now register the SSH public key for my new workspace in GitHub:

Come back to the terminal and press return to run the Ansible playbook that clones all my repos:

Now, let’s run the Hugo server:

And access it securely by clicking on the link within Coder:

See my changes in a browser on my client.

Thick or Thin Client?
I’m very pleased with my automated development environment powered by Coder. The Thin Client model is perfect for when I am traveling if I want to do some development. I self-host Coder on my own server in my home lab. Of course, this presents an access challenge as my home lab is tucked neatly and safely behind the firewall. I solved this challenge using Twingate. Even when I’m at home, I may still use the Thin Client model for quick experiments because it provides isolation from other workspaces enabling me to test upgrades or different operating systems without the risk of breaking anything.
The Thin Client model is arguably much more secure in the event of a lost or stolen laptop. With a Thick Client model, the entire source code plus probably secrets in the form of SSH keys become theoretically accessible to the thief. Of course, this threat can be mitigated using full-disk encryption, but still a stolen or lost laptop can create a headache for the security team.
The Thin Client model, when powered by Coder does make it very easy to accelerate new team member on-boarding significantly. While at Cisco, I had to scale my Engineering Organization very quickly using contractors. I could have used something like Coder in this situation. It also makes it easy to support a BYOD policy.
The Thick Client however, does provide some benefits. For example, If I want to do some graphics programming or if I want to directly access the hardware (i.e. my NVidia GPU) that is not available on my development server.
I also use a Neovim plugin called Markdown Preview to enable me to preview in the browser while working on Markdown files. This doesn’t work at the moment. There may be a way to securely open a remote browser on the client, but I’m ok without it for now (and no, running a remote X11 server is probably not a good idea).