Development Environment Setup
Despite my colleagues' pressure, I’m still doing development work at home using Linux virtual machines on Windows.
This configuration provides clean, isolated, customizable, portable, and backupable development environments. It also keeps the host machine tidy and prevents any libraries, applications, or tools installed just for testing from leaving residue on the main system.
However, it also has some drawbacks, such as network management, keeping the virtual operating systems up-to-date, file transfer, and disk management.
At the end of this article, you’ll find the steps to set up a development environment using Hyper-V on Windows 10 Professional or Enterprise.
Prerequisites and Version Matrix
| Component / Tool | Tested Version | Notes / Requirements |
|---|---|---|
| Host OS | Windows 10 Enterprise 22H2 | Hyper-V must be active, virtualization enabled in BIOS |
| Hyper-V | 10.0.22621 | "Hyper-V Management Tools" and "Hyper-V Platform" enabled |
| Guest OS | Linux Mint 21.3 XFCE (Ubuntu 22.04 based) | UBUNTU_CODENAME=jammy |
| PowerShell | 7.4.4 | Updated version via Winget |
| VS Code | 1.94.0 | Version installed via apt repo |
| Docker | 27.2.0 | Ubuntu repository is used for Mint |
| NodeJS / NVM | Node 22.11.0 / NVM 0.40.3 | Activates after restart |
| Go | 1.25.3 | Manual download and tar.gz installation |
| .NET SDK | 8.0 / 9.0 | Via apt or PPA:dotnet/backports |
| AWS CLI | v2.17.0 | Installation via zip method |
| Python | 3.10.12 | Comes with Mint |
Necessary Adjustments
Most of the features and tools that a developer needs or would make their work easier on Windows come disabled and uninstalled by default. First, these need to be enabled.
Installing Hyper-V
Enable-WindowsOptionalFeature -Online -FeatureName "Microsoft-Hyper-V-All" -NoRestart
Enable-WindowsOptionalFeature -Online -FeatureName "Microsoft-Hyper-V" -NoRestart
Enable-WindowsOptionalFeature -Online -FeatureName "Microsoft-Hyper-V-Tools-All" -NoRestart
Enable-WindowsOptionalFeature -Online -FeatureName "Microsoft-Hyper-V-Management-Powershell" -NoRestart
Enable-WindowsOptionalFeature -Online -FeatureName "Microsoft-Hyper-V-Hypervisor" -NoRestart
Enable-WindowsOptionalFeature -Online -FeatureName "Microsoft-Hyper-V-Services" -NoRestart
Enable-WindowsOptionalFeature -Online -FeatureName "Microsoft-Hyper-V-Management-Clients" -NoRestartThese commands install the Hyper-V service and the necessary management tools.
PowerShell Update
winget install Microsoft.Powershell --accept-package-agreements --accept-source-agreementsThis command installs a more up-to-date version of PowerShell than the one that comes built into Windows.
Folder View
Push-Location
Set-Location HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced
Set-ItemProperty . HideFileExt "0"
Set-ItemProperty . Hidden "1"
Pop-LocationThis command makes file extensions visible and also ensures that hidden files are visible. This is totally optional. I consider it important to have this setting on my host machine, so I’m sharing it here.
Disabling UAC
New-ItemProperty -Path HKLM:Software\Microsoft\Windows\CurrentVersion\policies\system -Name EnableLUA -PropertyType DWord -Value 0 -ForceThis is another one of those suggested commands that you can perform. User Account Control (UAC) is a tool used to prevent unauthorized access on Windows. Disabling it could allow unauthorized code to run on your machine. I recommend considering this risk before performing the operation. On my machine, I prefer to have an antivirus software that provides protection similar to UAC but is configurable, so I disable the UAC feature.
Hyper-V Network Adjustment
Before setting up the environment, you need to allocate a network for Hyper-V and provide internet access. In this step, I’ll be using the 172.19.85.0/24 network. You can choose any IP configuration you prefer. This parameter has been made parametric.
$natSwitch = "LAN-85"
$natIPAddress = "172.19.85.254"
$natAddress = "172.19.85.0/24"
New-VMSwitch -SwitchName $natSwitch -SwitchType Internal
New-NetIPAddress -IPAddress $natIPAddress -PrefixLength 24 -InterfaceAlias "vEthernet ($natSwitch)"
New-NetNAT -Name $natSwitch -InternalIPInterfaceAddressPrefix $natAddressThis command creates an internal virtual switch, performs NAT to the host machine using the specified address, and gains internet access. It’s important to note that a gateway value should not be entered for the virtual switch created for the virtual adapter.
Creating a Virtual Machine
Now that the network adjustments have been made, you can proceed to creating a virtual machine.
$natSwitch = "LAN-85"
$vmName = "fth-template"
$workspacePath = "C:\\workspace\\machine\\"
$isoPath = "C:\\workspace\\iso\\linuxmint-21.3-xfce-64bit.iso"
$cpu = 2
$ramSize = 8GB
$diskSize = 50GB
New-VHD -Path "$workspacePath\\$vmName\\$vmName.vhdx" -SizeBytes $diskSize -Fixed -BlockSizeBytes 1MB
New-VM -Name $vmName -MemoryStartupBytes $ramSize -BootDevice VHD -VHDPath "$workspacePath\\$vmName\\$vmName.vhdx" -Path "$workspacePath" -Generation 2 -Switch $natSwitch
Set-VMFirmware $vmName -EnableSecureBoot Off
Set-VMProcessor -VMName $vmName -Count $cpu
Set-VM -Name $vmName -AutomaticCheckpointsEnabled $false -CheckpointType Disabled
Set-VMMemory -VMName $vmName -DynamicMemoryEnabled $true -MinimumBytes ($ramSize / 16) -StartupBytes ($ramSize / 8) -MaximumBytes $ramSize
Add-VMDvdDrive -VMName $vmName -Path "$isoPath"
$dvd = Get-VMDVDDrive -VMName $vmName
Set-VMFirmware -VMName $vmName -FirstBootDevice $dvd
Start-VM -Name $vmNameDifferent virtual machines can be installed using the parameters in the above command. I’ll explain a bit more detailed what this command does.
Virtual Machine Settings
When creating the virtual machine, we create it as Gen 2. Gen 2 allows Hyper-V to perform virtualization using the physical resources that the host machine also uses. If we had used Gen 1, the CPU, RAM, and other hardware would be in the virtualization layer, causing the virtual machine to slow down.
Because the SecureBoot feature is not recommended on Linux machines, it’s disabled. As a personal preference, I also disable the Checkpoint feature to make the virtual machine take up less space. The disadvantage of this is that Hyper-V cannot receive checkpoints, so a restore operation cannot be performed later.
Disk Block Size Adjustment
Microsoft Hyper-V recommends that Linux operating systems run faster with virtual disks consisting of 1MB blocks. This value is the default 32MB and configured for Windows operating systems. To support this, it also recommends using an ext4 format consisting of 4096 groups for Linux.
The reason for this recommendation is to grow the virtual disk by smaller pieces instead of larger ones when it needs to grow, optimizing disk usage on the host machine. The reason for the Linux suggestion is to reduce the ratio of allocated but unused disk space.
Disk Type Adjustment
In addition to block adjustment on the disk, I prefer to make adjustments in the disk type as well. For this, I set the disks as -Fixed when creating the virtual machine. With this parameter, the specified amount of space is reserved for that virtual disk on the host machine. This reduces the installation time of the virtual machine.
RAM and CPU Adjustment
The CPU setting for the virtual machine is done by giving a core value to the $cpu variable. The $ramSize variable is given the maximum possible memory value. When defining, an adjustment is made to ensure that the value given starts with 1/8 of the given value and can be up to the maximum given value. This way, the virtual machine requests and receives the necessary amount of RAM when it needs it.
DVD Boot
The operating system ISO file to be installed is provided as a DVD for the first installation using the $isoPath variable and added to the boot order.
Virtual Machine Installation
In those times when I initially set up this environment structure, I used to install Ubuntu on each machine and continue. Later, due to my desire to install more compact virtual machines, I updated my virtual machines with Linux Mint.
Linux Mint is a distribution based on Ubuntu, taking up less space and supporting Ubuntu packages. I prefer to use it because it's both Ubuntu and not Ubuntu for my purpose. While installing Linux Mint, it offers different desktop structures, but I still choose the smaller Xfce version.
After the virtual machine starts, a setup screen will appear shortly, follow the steps on this screen to complete the installation. Then you can continue reading from here for further customization.
Cleanup
Every Linux operating system, unless otherwise specified, comes with some pre-installed software. Most of this software is built with the end user in mind. Since I don't typically use these, I perform a preliminary cleanup with the following commands.
sudo apt remove --purge -y libreoffice* redshift warpinator pix hexchat thunderbird transmission-gtk webapp-manager celluloid hypnotix rhythmbox timeshift simple-scan vim-tiny hplip youtube-dl xfce4-dictAfter cleaning, I also clean up the remaining parts and perform updates.
sudo apt clean && sudo apt autoremove && sudo apt update && sudo apt upgrade -yAs with any major operation, a restart will be required.
sudo rebootBasic Software
sudo apt install -y openssh-server vim gitI'm installing 3 software that I’ve determined to be necessary in all the developer machines I’ve built so far with this command.
- OpenSSH Server: To share files
- Vim: Text editor used in CLI
- Git: Version control tool used to get and send code.
Security
Even though it's a virtual machine, the code and information contained within it may be important or confidential depending on the work being done. Therefore, I think virtual machines should use basic security tools.
sudo systemctl start ssh
sudo systemctl enable ssh
sudo systemctl status ssh
sudo ufw allow ssh
sudo ufw enable
sudo ufw reloadThis command adjusts the firewall software and enables the SSH service within the virtual machine. Thanks to these settings, unauthorized requests from outside are prevented from reaching the virtual machine.
Network Settings
The last setting required for the virtual machine to run independently is the network configuration. This can be done via GUI or CLI. I will only explain what needs to be done basically.
Since Hyper-V doesn’t provide a DHCP server, we need to manually assign an IP address to our virtual machine.
Device: eth0 Method: Manual Address: 172.19.85.XXX Netmask: 24 Gateway: 172.19.85.254 DNS servers: 1.1.1.1,8.8.8.8
The values above are what will be used. You need to enter a value between 2-253 for the XXX field. If you have a different netmask value, you must choose an IP address accordingly.
In simple terms, we entered 172.19.85.254 as the gateway to connect the virtual machine to the internet. As remembered, this is the NAT address used when connecting our virtual switch to the host machine. Thus, the virtual machine can directly access the internet.
After making network changes, you need to ensure that the changes are applied by running the following command.
sudo service NetworkManager restartDeveloper Environment
The preparations for the virtual machine structure are complete. The virtual machine is now ready for use. Let's start installing development tools according to needs and requests.
VS Code
I like to use VS Code as an IDE when developing. I prefer it because of its flexibility and ability to be developed by adding plugins. It’s also a reason why it’s free.
sudo apt install dirmngr ca-certificates software-properties-common apt-transport-https -y
curl -fSsL https://packages.microsoft.com/keys/microsoft.asc | sudo gpg --dearmor | sudo tee /usr/share/keyrings/vscode.gpg > /dev/null
echo deb [arch=amd64 signed-by=/usr/share/keyrings/vscode.gpg] https://packages.microsoft.com/repos/vscode stable main | sudo tee /etc/apt/sources.list.d/vscode.list
sudo apt update && sudo apt install -y codeThe above command installs VS Code manually from the apt repositories.
When working with VS Code on Linux, it tries to understand if there are any changes in the files by following them. When the number of files increases significantly, the ENOSPC error starts to be received. The following command needs to be run to fix this.
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -pDocker
Even if it’s a virtual machine, I prefer to use docker when I need to temporarily use some software. The important thing to note when installing Docker is that Linux Mint is based on Ubuntu.
Normally, the part defined as VERSION_CODENAME in the script is used as UBUNTU_CODENAME for Linux Mint. Because Linux Mint updates its version information with the VERSION_CODENAME value and using it will cause Docker to not find the relevant package. Therefore, the UBUNTU_CODENAME value is used.
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$UBUNTU_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt updateThis command cleans up any packages that may have been installed previously and makes preparations.
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginThis command performs the installation and then the following command is used to bring the services into operation.
sudo systemctl enable docker.service && sudo systemctl enable containerd.serviceCurrently, you can only access Docker commands with sudo. If you want to access it without sudo, you need to run the following commands.
sudo groupadd docker
sudo usermod -aG docker $USER
newgrp dockerIn this command, the $USER value represents the user who is currently logged in. If you want to add a different user, you need to write the desired username instead of $USER.
It may be necessary to restart the terminal you are using for this operation to take effect.
NodeJS
With the hype it has been receiving lately, NodeJs, which is used for development, requires some installations as well.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
sudo rebootThis command installs NVM (Node Version Manager), which allows you to easily manage NodeJs versions. A restart is required after this process.
After the restart, you can install the desired version by running the following command:
nvm install 22.11.0
nvm use 22.11.0This command will load node and npm onto the machine and make them ready for use. However, if you want to install a package manager like yarn;
corepack enable yarnrun this command. With this command, yarn will also be installed on the machine.
Go
Let's look at how to install the Go programming language, which has been getting a lot of attention lately. The installation is quite simple. For example, the following command needs to be run:
sudo rm -rf /usr/local/go
wget https://go.dev/dl/go1.25.3.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.25.3.linux-amd64.tar.gzAfter the installation, add the following line to the $HOME/.profile or /etc/profile file. A restart will be useful again.
export PATH=$PATH:/usr/local/go/bin.NET
Installing .NET is as simple as installing other languages. However, there are some differences between versions, so you need to pay attention.
sudo apt install -y ca-certificates libc6 libgcc-s1 libgssapi-krb5-2 libicu70 liblttng-ust1 libssl3 libstdc++6 zlib1gThe above command installs the libraries needed for .NET. Then, run the appropriate command according to the version.
.NET 6.0
sudo apt update && sudo apt install -y dotnet-sdk-6.0 aspnetcore-runtime-6.0 dotnet-runtime-6.0.NET 8.0
sudo apt-get update && sudo apt-get install -y dotnet-sdk-8.0 aspnetcore-runtime-8.0 dotnet-runtime-8.0.NET 9.0
sudo add-apt-repository ppa:dotnet/backports
sudo apt-get update && sudo apt-get install -y dotnet-sdk-9.0 aspnetcore-runtime-9.0 dotnet-runtime-9.0After the installations are complete, you can add the following code snippet to the ~/.bashrc file for command completion support.
# bash parameter completion for the dotnet CLI
function _dotnet_bash_complete()
{
local cur="${COMP_WORDS[COMP_CWORD]}" IFS=$'\n' # On Windows you may need to use use IFS=$'\r\n'
local candidates
read -d '' -ra candidates < <(dotnet complete --position "${COMP_POINT}" "${COMP_LINE}" 2>/dev/null)
read -d '' -ra COMPREPLY < <(compgen -W "${candidates[*]:-}" -- "$cur")
}
complete -f -F _dotnet_bash_complete dotnetGit & GitHub
I keep all my developments on GitHub. In fact, I also host my blog there.
There are some things that are mandatory and recommended when working with Git and GitHub. Since I consider these as part of the development environment, I want to share them.
git config --global user.name "Fatih Tatoğlu"
git config --global user.email "[email protected]"
git config --global init.defaultBranch master
git config --global commit.gpgsign true
git config --global tag.gpgSign trueThe first two lines of the above command are a necessity for Git, and the other lines are my recommendations. Don’t forget to adjust the values according to yourself.
gpg --full-generate-key
gpg --list-secret-keys --keyid-format=long
gpg --armor --export <key-id>
git config --global user.signingkey <key-id>This is the first step in the process of generating a PGP key to enable code signing. In this step, copy the value of the key generated between -----BEGIN PGP PUBLIC KEY BLOCK----- and -----END PGP PUBLIC KEY BLOCK-----.
Press the "New GPG key" button on GitHub Keys and write the copied value in the "Key" field on the opened window. You can write whatever you want in the "Title" field.
Now your code will be signed from your machine.
In addition, I recommend using a PAT for development to avoid sharing token information openly.
- repo
- write:packages
- read:org
- write:public_key
- write:gpg_key
- workflow
- write:ssh_signing_key
A classic token with a maximum lifespan of 1 year will do the job. You can get this token from GitHub Developer Tokens.
For ease of use and to prevent sensitive information from appearing in history, you can add the following lines to the ~/.bashrc file:
export GH_USERNAME=fatihtatoglu
export GH_TOKEN=ghp_....This allows you to clone with git clone http://$GH_USERNAME:[email protected]/fatihtatoglu/... without explicitly sharing the token information.
AWS
When developing for AWS, I often use the AWS CLI. Running the following command installs AWS CLI V1 for Ubuntu and Mint:
sudo apt install awscli -yHowever, because it has more features and is newer, I prefer to install AWS CLI V2 using the following command.
Python
Python has become the standard when working with artificial intelligence these days. Therefore, it may be necessary to install Python as well.
The good thing is that Linux Mint comes with Python 3.10 by default. It’s enough for me to just install the necessary tools on top of it.
sudo apt update && sudo apt upgrade && sudo apt install python3 python3-venv -yTroubleshooting
I’ve scattered solutions for potential issues throughout this article, but to consolidate them in the below.
ENOSPC Error
This error appears when VS Code runs out of limits while watching files on Linux.
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -pRunning this command increases the watch limit.
Running Docker Commands Without sudo
After installing Docker, it won’t allow you to perform operations without sudo privileges.
You can solve this problem using the following command:
sudo groupadd docker
sudo usermod -aG docker $USER
newgrp dockerNo Network Access / Unable to Get an IP Address
If your virtual machine cannot access the network or obtain an IP address after you’ve completed the setup, check the following steps:
- Is the Gateway value set to
172.19.85.254? - Has the DNS value been assigned correctly?
- Does the IP Address conflict with another virtual machine?
Linux Mint Won’t Boot
This may be because Secure Boot was left enabled.
$vmName = "fth-template"
Set-VMFirmware $vmName -EnableSecureBoot OffAfter running this command, Secure Boot will be disabled. However, the virtual machine needs to be restarted for it to take effect.
Snapshot / Checkpoint
We disable the snapshot/checkpoint feature during installation, which prevents restore operations.
However, if you want to create a backup, you can enable this parameter or take a backup of the virtual machine using Export-VM.
Closing Remarks
I often need different development environments every day. Having a simple and mostly pre-defined guideline for this makes my job much easier. Especially being able to run a small machine without paying any extra fees is very appealing.
The same thing can be done with different virtualization layers, and even a complete lab setup can be created. There are no obstacles to that. I’m just saving these options for other articles.
If you would like to contribute, you can share the steps you need or use, or forward this article to people who you want to read it, or leave your likes and comments.