Ian Cleary RF Systems Design Engineer

Satellite Logo

Virtualizing development on Windows Hosts and why I use VirtualBox

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:

  • Path is set by the development environment and not shared betwen Windows and the development (WSL feature to share path).
  • Networking avoids dealing with nameservers and configuration files when dealing with private nameservers (Windows host configuration is a pain, WSL overwrites some host files)
  • Consistent IP Address of development environment (Hyper-V guests not having static IP addresses across reboots)

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.

Docker Desktop

I explored [devcontainers][/devcontainer], 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 npm install or pip 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.

Hyper-V

Windows Subsystem for Linux (WSL)

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)

VirtualBox

This is my preferred option. It is not perfect, but it is the best option I have found.

I get the following benefits:

  • Windows host applications (Outlook, Office, etc.) which, in a Windows dominated environment, are required for my work.
  • Consistent networking and DNS resolution. I can use the same DNS servers as my host and do not have to worry about the VPN client.
  • Port Forwarding to the host. For example, we can setup port 22 on the guest to forward to 3022 on the host, which allows consistent ssh connections, with no need to know the IP address of the guest.
  • Pure Linux Environment. I can use the same tools and setup as I would on a Linux host, there is no interference with Windows.
  • No Docker Desktop license required. Docker Desktop is free for personal use, but requires a license for commercial use. I do not have to worry about that with VirtualBox, since Docker virtualization on a Linux OS is free.

SSH port forwarding

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:

Screenshot of Virtualbox Port Forwarding pop up modal with Host port 3022 and Guest Port 22 set.  IP Addresses are empty. Name is a placeholder.

Fill it out as follows:

NameProtocolHost IPHost PortGuest IPGuest Port
SSHTCP302222

Note:

  • Host IP is set to nothing - this is on purpose, and is equivalent to saying 0.0.0.0. It means that ANY MACHINE that can access your Host on TCP port 2222 will be able to talk to the SSH on your guest. Be careful! 1
  • Guest IP is left blank - there is no need by default to change this.

What the above rule means is:

"If a TCP connection is received on the Host on TCP port 2222, 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:

  • Shut down the VM (Close -> ACPI Shutdown)
  • Quit VirtualBox
  • Restart VirtualBox
  • Start the VM

You will need to setup the ssh key pair on the guest and host!

Test the ssh connection

ssh -p 3022 username@127.0.0.1
ssh -p 3022 username@127.0.0.1

username is a placeholder!

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).

SSH config file

This can be made one step easier by adding the following to your ~/.ssh/config file:

Host myvirtuaboxguest
HostName 127.0.0.1
Port 3022
User username
IdentityFile ~/.ssh/virtualbox_keypair_id_rsa
Host myvirtuaboxguest
HostName 127.0.0.1
Port 3022
User username
IdentityFile ~/.ssh/virtualbox_keypair_id_rsa

This assumes ~/.ssh/virtualbox_keypair_id_rsa.pub is setup on the guest as the authorized key for the user "username".

You can then connect to your guest by simply typing:

ssh myvirtualboxguest
ssh myvirtualboxguest

Example modular setup

In 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:

Include config.d/*
Include config.d/*

Then my ~/.ssh/config.d/myvirtualboxguest file looks like this:

Host nixos.local
HostName 127.0.0.1
Port 3022
User iancleary
IdentityFile ~/.ssh/nixos.local.pub
Host nixos.local
HostName 127.0.0.1
Port 3022
User iancleary
IdentityFile ~/.ssh/nixos.local.pub

My ssh connection from the host:

ssh nixos.local
ssh nixos.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.

Note: that immutable file system of NixOS is a great feature, but it does cause incompatibilities with remote development in VS Code. I have not found a work around for this. 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!

Thanks and I hope this helps!