Beyond the Windows

A guided tour of my development environment

Web developers at work; late work of Leonardo da Vinci

Sometimes I feel like an endangered species among all the half-eaten apples, so I'll tell you a little bit about what my development environment looks like as a (web) developer on Windows.

As the rays of the Sun slowly penetrate the three layers of tempered glass, one of the most extraordinary creatures in the northern hemisphere can be seen - Scriptor fenestralis, better known as the web developer on Windows.

Their natural habitat is an ideal combination of artificial lighting and cool temperatures, creating an optimal environment for mentally challenging work.

In short: it's a Windows host machine running a Debian-based virtual machine with Hyper-V. Within that, I use Docker to run specific projects. It's not fancy technology by any stretch of the imagination, but I haven't had the urge to experiment with WSL2 more seriously. But let's get into the details.

The physical machine

I used to use VirtualBox, but then I switched to Hyper-V, because it's already included in Windows, you just have to turn it on. Also, I thought, if it's an official virtualization platform, it might work better. I don't have anything to back this up, one thing I can think of is that the virtual machine starts when the machine starts, but maybe you could do that with VirtualBox as well.

Networking

If you want to access your virtual machine (or the Internet from your virtual machine), you need to set up a network for it. Here we can go in two directions, we can use either an External switch or an Internal switch (there is also a Private switch, but that doesn't help us now).

On my desktop machine, I went with the External switch option, connected it with the network card that the machine gets Internet on and that's pretty much it. The virtual machine also appears to the router as if it were a physical machine on the network. Based on its MAC address, I gave it a fixed IP address on the DHCP server and a host on the DNS server that resolves to that IP address (devbox.lan).

This may be an acceptable solution for a desktop machine that is rarely moved, but what about a laptop for example where you may not have access to all the routers to configure this? An Internal switch could work in this case. I configured it with the following PowerShell commands:

> New-VMSwitch -SwitchName "Internal" -SwitchType Internal
> New-NetIPAddress -IPAddress 192.168.56.1 -PrefixLength 24 -DefaultGateway 192.168.56.1 -InterfaceAlias "vEthernet (Internal)"
> New-NetNAT -Name "InternalNatNetwork" -InternalIPInterfaceAddressPrefix 192.168.56.0/24

We are using the 192.168.56.0/24 subnet, but without DHCP we don't get an IP address automatically. We have to specify a fixed IP address inside the virtual machine. For Debian, something like this in /etc/network/interfaces should work:

iface eth0 inet static
  address 192.168.56.101
  netmask 255.255.255.0
  gateway 192.168.56.1

If you also need a host for it, you can add the following line to the C:\Windows\System32\drivers\etc\hosts file:

192.168.56.101 devbox.lan

Sometimes it is necessary to access a port of the virtual machine on localhost (if something inside the virtual machine is running on port 8080, I can access it on localhost:8080). To do this, I initially used the following PowerShell command:

> Add-NetNatStaticMapping -NatName "InternalNatNetwork" -Protocol TCP -ExternalIPAddress 0.0.0.0 -InternalIPAddress 192.168.56.101 -InternalPort 8080 -ExternalPort 8080

This started to not work after a while. I don't know what happened to it, but after some digging, I found another command instead.

> netsh interface portproxy add v4tov4 listenport=8080 listenaddress=0.0.0.0 connectport=8080 connectaddress=192.168.56.101

In hindsight, it might have been easier to just use SSH port forwarding. What a delightful discovery to make during writing this post.

GUI applications

The physical machine is running VcXsrv, which is an X server running on Windows. Within the virtual Linux, I can use it to launch windowed applications that can be used as Windows applications. Usually the IDE I'm currently using runs inside the virtual machine with this method because it's easier to access Linux/Docker stuff inside the virtual machine and there are fewer problems around file permissions.

SSH

I use a GPG key stored on a YubiKey for SSH authentication. The GPG agent in Gpg4win is configured to both handle the YubiKey and to offer the key to the SSH agent:

scdaemon.conf
reader-port Yubico Yubi
pcsc-shared
disable-application piv
gpg-agent.conf
enable-ssh-support
enable-putty-support

PuTTY is used as SSH client (although Windows Terminal is quite promising, but last time I checked it didn't want to work with the GPG agent). Agent forwarding[1] is enabled so that the virtual machine can use the key on the YubiKey.

Also, my Linux home directory is mounted as a network drive (P:\) to make it easier to move files between the two machines.

The virtual machine

This part is pretty basic, a simple Debian or Ubuntu server that I set up using Ansible. After SSHing in, I'm greeted by a Bash with the default settings (apart from a few aliases) and I usually start a Tmux alongside. If I'm in the mood, I'll use Powerline (and its associated DejaVu Sans Mono font) to make them look fancy a bit.

Samba

There is a Samba on the machine because of the network drive. I found some performance-boosting settings on the net that I use with it:

/etc/samba/smb.conf
read raw = yes
write raw = yes
socket options = TCP_NODELAY IPTOS_LOWDELAY SO_RCVBUF=131072 SO_SNDBUF=131072 SO_KEEPALIVE
use sendfile = yes
aio read size = 16384
aio write size = 16384
oplocks = yes
max xmit = 65535
dead time = 15
getwd cache = yes

Docker

Last but not least, Docker is added for the projects. All the other stuff will hopefully run inside Docker. Speaking of Docker, we should talk a bit about its network setup.

If you let it run wild, there's a small chance that sooner or later it will create a network that conflicts with one of your other local networks and things will start to get weird. To prevent this, it's worth adding something like this to your settings:

/etc/docker/daemon.json
{
  "bip": "172.20.0.1/16",
  "default-address-pools": [
    {"base": "172.21.0.0/16", "size": 24}
  ]
}

So you can have ~250 networks with ~250 machines per network, which is probably more than enough for a development machine, but you can add more domains to the default-address-pools section if you run out of them.

That brings us to the end of the tour, I hope you enjoyed the trip. We've scratched the surface of quite a lot of things, but this is probably enough to get you started on this bumpy road.
Having said that, I can probably admit now that I'm not sure I could recommend this setup to anyone. I am comfortable with it enough not to change for the time being, but I am still looking for other possible alternatives.


Notes

1. It is often said that agent forwarding is not a good idea, because the socket will be available to others on the target machine if they have enough privileges (e.g. root), but this is not a threat in our case.

Ez a bejegyzés magyar nyelven is elérhető: Az ablakokon túl

Have a comment?

Send an email to the blog at deadlime dot hu address, or visit the tweet related to this post.

Want to subscribe?

We have a good old fashioned RSS feed if you're into that, and you can also follow the blog on Twitter.