Over the lasts year or so, I have explored ways to get my development environment consistent and deterministic. That has let me to package the tools and applications I use in a variety of ways. While I have control of my host operating system at home, that is not always the case.
This article is for those that have a Windows host and who desire to have a stable development environment. What do I mean by stable:
Note: these are my evaulations as of August 2023. There may have been fixes or changes in scope to any of these tools between now and when I wrote this.
I know I am not alone in having a Windows laptop that is operating while in a corporate network by being physical on-premise or by using a VPN.
My goal is to get a setup that works consistently for my use case. I don't want to have to adjust my setup anytime I change location.
We can do better, we can be lazier, we can have better things.
First I will explore what has not worked for me and why.
I explored devcontainers, which were generally sufficient in that they do perform their function. They encapsulated my development environment into a container and allowed me to run it. Version control repositories can contain them and that is great. It allows one to share a working setup.
In practice, they do work, but I found it very easy to have the startup time and development loop of using dev containers be quite tedious. It would take minutes to use an image in a source repository
that needs to pnpm install or uv install dependencies. These input/output expensive operations would take a very long time on Windows or Mac. Both use a virtual machine and mounts to present the docker container to the development environment.
You can cache this by setting up a volume within a docker compose file for the local environment, but you still have a loop where the initial setup of the repo takes too long for me. It is an interesting idea to get some initial integration and testing work done.
Doesn't seem to allow for consistent IP addresses across reboots. I have not found a way to do this. I have tried to set up a static IP address on the guest, but it does not seem to work.
If you want a graphical session only, this is an option. That is not my use case.
The speed of Hyper-V is great and something to fixate over, but the lack of consistent IP addresses is a non-starter for me. The consistency of other requirements takes precedence over speed.
I do not recommend this option.
WSL shares path with Windows. It consistently overwrites my wsl.conf and network configuration files on startup.
For those that are using command line interfaces (CLI) or simple tools, it is a great option. I have used it for that purpose and it works well.
When you start to have more complex DNS and networking use cases while on a VPN, I generally have had a bad time. I consider it a non-starter for my use case.
Note: WSL 1 has more dependence on the windows host networking and was slower but more reliable for me. WSL 2 is faster but less reliable, only due to my use case (internal DNS servers and Corporate networking with a VPN client)
Generally this is not worth the head ache if you work outside of your office at any non-zero frequency, such that you have even more friction when starting to do work.
This is my preferred option. It is not perfect, but it is the best option I have found given the trade preferences for reliability over toiling for speed.
I get the following benefits:
What we are going to do is pick a port on our Host, for example 3022, and forward TCP connections received on this port, to port 22/TCP (SSH) on our guest.
To do this, click on the green (+) button on the right.
Once you click it, a new row appears, ready to be filled out:

Fill it out as follows:
| Name | Protocol | Host IP | Host Port | Guest IP | Guest Port |
|---|---|---|---|---|---|
| SSH | TCP | 3022 | 22 |
Note:
What the above rule means is:
"If a TCP connection is received on the Host on TCP port 3022, send it on to the Guest on TCP port 22"
To apply the changes, click OK.
You are back in the Network Settings windows. Click OK again to exit the Network Settings.
You will probably need to restart the Virtual Machine so that the changes are applied:
You will need to setup the ssh key pair on the guest and host!
Test the ssh connection
ssh -p 3022 iancleary@127.0.0.1
iancleary is a placeholder for your preference!
Voila! You are now able to connect to your guest via SSH, without having to know its IP address.
If you have multiple guests, you can setup multiple port forwarding rules, each with a different Host Port, and all having the same guest port (22, assuming you don't change that on the guest's ssh server).
This can be made one step easier by adding the following to your ~/.ssh/config file:
#~/.ssh/config
Host development.local
HostName 127.0.0.1
Port 3022
User username
IdentityFile ~/.ssh/development_local_id_ed25519
This assumes
~/.ssh/development_local_id_ed25519.pubis setup on the guest as the authorized key for the user "username".
You can then connect to your guest by simply typing:
ssh development.local
code () { VSCODE_CWD="$PWD" open -n -b "com.microsoft.VSCode" --args $* ;}
code . to open the current directory, or code ~/Development/docsIn my case, I have multiple guests, so I have multiple entries in my ~/.ssh/config file, one for each guest.
My recommendation is to add the directive Include config.d/* in your ~/.ssh/config file, and then create a file for each guest in the ~/.ssh/config.d/ directory.
Verbosely, my ~/.ssh/config file looks like this:
#~/.ssh/config
Include config.d/*
Then my ~/.ssh/config.d/development_local file looks like this:
#~/.ssh/config.d/myvirtualboxguest
Host development.local
HostName 127.0.0.1
Port 3022
User iancleary
IdentityFile ~/.ssh/development.local.pub
My ssh connection from the host:
ssh development.local
The SSH config file is also read by tools such as Visual Studio Code, so the remote development extensions will use the same configuration.
If you want to use remote development in VS Code, I recommend using an Debian based distribution, such as Ubuntu or PopOS. Fedora and other rpm based options are also good choices!
Converting from VirtualBox dynamic vdi file to fixed (for I/O performance)
/c/Program\ Files/Oracle/VirtualBox/VBoxManage.exe list hdds
UUID: 84bb27a0-023d-4319-9e79-2187b4be6a95
Parent UUID: base
State: created
Type: normal (base)
Location: C:\Users\iancleary\Development\Virtualbox\Development\Development.vdi
Storage format: VDI
Capacity: 204800 MBytes
Encryption: disabled
This was a meant to be a 200 GB fixed file, but it defaults to standard, which is dynamic.
Make sure you close the medium first.
/c/Program\ Files/Oracle/VirtualBox/VBoxManage.exe closemedium 84bb27a0-023d-4319-9e79-2187b4be6a95
Below, I have renamed the original to DevelopmentDynamic.vdi, which isn't needed but I wanted to be explicit.
/c/Program\ Files/Oracle/VirtualBox/VBoxManage.exe clonemedium disk DevelopmentDynamic.vdi Development.vdi -variant Fixed
-variant Standardwould be back to dynamically allocated.
Then delete the original
/c/Program\ Files/Oracle/VirtualBox/VBoxManage.exe closemedium 84bb27a0-023d-4319-9e79-2187b4be6a95 --delete
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
/c/Program\ Files/Oracle/VirtualBox/VBoxManage.exe list hdds
UUID: d11e712f-fa19-4650-b524-88bec55ad068
Parent UUID: base
State: locked write
Type: normal (base)
Location: C:\Users\icleary\Development\Virtualbox\Development\Development.vdi
Storage format: VDI
Capacity: 204800 MBytes
Encryption: disabled
$ ls -al | grep Development.vdi
-rw-r--r-- 1 VIASAT+icleary 4096 **214750461952** Oct 10 09:44 Development.vdi
Then use the User Interface (UI) of VirtualBox manager to select the right VDI file.
/c/Program\ Files/Oracle/VirtualBox/VBoxManage.exe setproperty defaultfrontend headless
Thanks and I hope this helps!