Introduction
Hello I'm Jimmy. This is a collection of notes and configurations that I've accumulated while setting up my development environment for day-to-day productivity.
I work with both Windows and macOS, and I rely heavily on Linux environments within containers or VMs. This site serves as a central repository for the things I learn while configuring these environments.
I'm making this information public in the hope that others might find it useful. Feel free to browse around, and I hope you find something that helps you in your own setup!
Windows
Windows has evolved into a powerful desktop environment, especially with the introduction of WSL2.
Essential Windows Productivity Tools
- Powertoys: Utilities like FancyZones for window management, and Command Palette for quick app launching streamline my workflow.
- Windows Terminal: A modern, customizable terminal that integrates seamlessly with WSL and support multiple profiles, themes, and keyboard shortcuts.
- Hurl: Hurl is a utility that lets you choose which browser (like Firefox or Edge) to open when you click a link outside your browser.
Useful Resources
WSL2
Installation
Install wsl with the following command:
wsl.exe --install
Install FedoraLinux-42 distribution:
wsl.exe --install FedoraLinux-42
Keep your WSL installation updated by running:
PS E:\clones\homelabguide> wsl.exe --update
Checking for updates.
The most recent version of Windows Subsystem for Linux is already installed.
Configuration
Resource Allocation
Create or modify .wslconfig
file to customize WSL2 resource allocation:
cd ~
notepad.exe .wslconfig
Here is a sample configuration:
# Settings apply across all Linux distros running on WSL 2
[wsl2]
memory=4GB
processors=4
swap=4GB
networkingMode=mirrored
dnsTunneling=true
autoProxy=true
[experimental]
autoMemoryReclaim=gradual
I'm not going into details why I configure with these settings, but you can read more about it here.
After making changes to .wslconfig
, apply them by shutting down WSL:
wsl.exe --shutdown
Disable windows interop
I experience slowness when typing in the WSL terminal while the interop appendWindowsPath
is enabled. You can disable it by configuring /etc/wsl.conf
file:
[interop]
appendWindowsPath=false
Read more about it here
Accessing Your WSL Environment
Enter your Fedora Linux environment:
wsl.exe -d FedoraLinux-42
SSH Setup
Generate an SSH key for secure authentication:
ssh-keygen
To share your existing Windows SSH keys with WSL:
wsl -d FedoraLinux-42
cp -rv /mnt/c/Users/{replace_with_your_username}/.ssh/ ~
chmod 600 ~/.ssh/id_ed25519
Make sure to replace {replace_with_your_username}
with your actual Windows username.
Key Principles
- Always think Window and WSL as two separate operating systems.
- Windows files are mounted inside WSL under
/mnt/c
, but this is actually a mounted Windows file system, not native Linux storage. - To avoid perfomance issues do not work with Windows files from Linux and avoid editing WSL files directly from Windows. If you need to sync files between the two environments, consider using a remote git repository or a dedicated syncroization tool like Mutagen.io, as these methods are more reliable for cross-platform workflows.
Useful Resources
- Fedora Linux is now an official WSL distro
- Sharing SSH keys between Windows and WSL
- WSL2, Visual Studio Code, Windows 10, Ubuntu
- Run Linux Workflows on Windows!
- WSL for Linux Deep Dive
- A Linux Dev Environment on Windows with WSL2
Winget
To install winget, checkout this guide: https://learn.microsoft.com/en-us/windows/package-manager/winget/
Using Windows 10/11 IoT Enterprise LTSC user, without Microsoft Store, Follow this guide instead: https://learn.microsoft.com/en-us/windows/iot/iot-enterprise/deployment/install-winget-windows-iot
Installing apps with winget, First search for what you want:
PS C:\Users\Jimbo> winget search espanso
Name Id Version Source
---------------------------------------
Espanso Espanso.Espanso 2.2.3 winget
Then install it:
winget install --id Espanso.Espanso
Powertoys
Powertoys utilities that I find useful:
- Command Pallete
- FancyZones
- ZoomIt
Command Pallete lets you run apps by typing their name - similar to Spotlight on macOS.
FancyZones is help to create window layout. Press Win + Shift + ` to open the FancyZones editor.
I use 4 virtual desktops for different tasks:
- Terminal
- Code
- Browser
- Other
Switch between the virtual desktop using Ctrl + Win + Arrow left/right.
To jump to any open window from any desktop, use Command Pallete by typing < before the app name.
This setup is heavily inspired by DHH in the Omakub demo.
Windows Terminal
Configuration
configure, Catppuccin theme please follow the instruction in catppuccin
Change bell notification style to Flash taskbar, I find audible bell notification is annoying. Go to Settings > Defaults > Advanced > Bell notification style > and check Flash taskbar.
Add a new profile for a remote machine
Go to Settings > Open JSON file
in your editor of choice, add a new profile by adding this json object under the profiles.list
array
{
"commandline": "ssh jimbo@devbox",
"name": "jimbo@devbox",
}
Install Nerdfont
Must install Nerd Fonts for the icons to work.
macOS
For macOS, I often work on a company-issued laptop but still need a Linux-like environment for development. Here's my streamlined setup:
- Lima-vm: My go-to for running Linux virtual machines on macOS. It's lightweight and supports x86_64 containers with the
--rosetta
flag, making it easy to run and manage Linux containers locally. - Wezterm: I use Wezterm because it reads your SSH config directly, allowing you to open a new tab and SSH into a remote machine instantly - no extra setup required. This makes managing multiple remote sessions fast and seamless.
- Amethyst: A tiling window manager that helps keep my workspace organized and simple keyboard shortcusts.
- Maccy: A clipboard manager that boosts productivity.
Lima-vm
Installation
Follow installation instructions for Macos.
Create a new VM with the following command:
limactl start template://docker --rosetta
--rosetta
flag is required for working with x86_64 containers, if you don't need it, you can omit it.
Accessing the VM with the following command:
limactl shell docker
Tunnel Access
If you want to browse using a browser that's aware of the network in the lima-vm instance, you can use the following command:
limactl tunnel --socks-port 1080 default
Later in the browser (I'm using firefox), you can set the SOCKS proxy to socks://localhost:1080
.
Wezterm
Grab the latest release from here
Here is my lua config file:
local wezterm = require("wezterm")
local config = wezterm.config_builder()
config.color_scheme = "catppuccin-latte"
config.font = wezterm.font("Jetbrains Mono")
config.keys = {
{
key = "Enter",
mods = "ALT",
action = wezterm.action.DisableDefaultAssignment,
},
}
-- https://github.com/wez/wezterm/discussions/4728
local is_darwin <const> = wezterm.target_triple:find("darwin") ~= nil
if is_darwin then
config.font_size = 16.0
else
config.font_size = 14.0
end
return config
Amethyst
I keep all windows fullscreen using the Fullscreen layout and navigate between them with Ctrl + Cmd + Arrow right/left.
I only use these 2 layouts:
- Fullscreen
- Two Panes
Fullscreen is my go-to most of the time.
Some apps like system settings work better floating, so I add them to the float list.
The apps that I keep it the float list:
- Telegram
- Appstore
- Finder
- Maccy
- Logseg
This setup is heavily inspired by DHH in the Omakub demo.
Homelab
This section covers tools available in Linux, especially related to containers, Kubernetes, and self-hosted apps.
Podman
Installation
Install podman
sudo dnf install -y podman
Compatibility with Docker
Install podman-docker
sudo dnf install -y podman-docker
Install single binary docker-compose
curl -SL https://github.com/docker/compose/releases/download/v2.36.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
Make it executable
sudo chmod +x /usr/local/bin/docker-compose
Enable podman socket
systemctl --user --now enable podman.socket
Find your podman socket address
podman info | rg remote -A2
Set DOCKER_HOST
environment variable
export DOCKER_HOST=unix:///run/user/1000/podman/podman.sock
You'll need to set the DOCKER_HOST
variable each time you open a new terminal, or just add it to your .bashrc
file.
Notes on Docker Compose with WSL
If you're running docker-compose and find an error like netavark (exit code 1): nftables error: "nft" did not return successfully while applying ruleset
it' likely because limitation in how nftables operates in WSL.
To fix this, switch podman to use iptables
as firewall driver.
Install iptables
sudo dnf install -y iptables
Open or create /etc/containers/containers.conf
, and add the following:
[network]
firewall_driver="iptables"
Running Podman in the Background
For containers you want to keep running, podman has systemd integration called Quadlet.
Example for a browserless container:
Create a systemd service file in ~/.config/containers/systemd/browserless.container
[Unit]
Description=Browserless
[Container]
Image=ghcr.io/browserless/chrome:v2.27.0
PublishPort=3000:3000
AutoUpdate=registry
PodmanArgs=--memory=1g --cpus=0.8
[Service]
Restart=always
[Install]
WantedBy=default.target
Reload systemd
systemctl --user daemon-reload
Start and enable the service
systemctl --user start browserless.service
systemctl --user enable browserless.service
Check status
systemctl --user status browserless.service
We have to enable the linger for our user to start the containers without the user being logged in:
loginctl enable-linger $USER
Podman Auto-Update
The AutoUpdate=registry
option helps update images automatically, but you still need to run:
podman auto-update
You can set up a cronjob to run this command regularly.
Useful Resources
- Install the standalone Docker Compose
- Container life cycle management in Edge deployments
- Going from containers to pods to Kubernetes
- Quadlet
- Setting up container registries
- Leaving the Dock
- Manage linger
- Quadlet and Inlets
- Containerized services on a home server
Multipass: Ubuntu VMs Made Simple
Overview
Multipass is a streamlined solution for creating and managing Ubuntu VMs locally. It offers a "just works" experience that's perfect for:
- Isolating different project environments
- Testing with different dependency versions (PHP, Node.js, Go, etc.)
- DevOps learning and experimentation
- Container operations with Podman
- Ansible automation practice
Installation
sudo snap install multipass
Verify installation:
multipass version
Basic VM Operations
Create VM with Custom Resources
multipass launch --name dev-vm -c2 -m4G -d20G "24.10"
-c2
: 2 CPU cores
-m4G
: 4GB RAM
-d20G
: 20GB disk
"24.10"
: Ubuntu 24.10 (includes latest Podman with Quadlet support)
Create VM with SSH Key
- Create
cloud-init.yaml
:
ssh_authorized_keys:
- YOUR_PUBLIC_KEY_HERE
- Launch with cloud-init:
multipass launch --name dev-vm -c2 -m4G -d20G --cloud-init cloud-init.yaml "24.10"
Connect to VM
Get VM IP address:
multipass list
SSH directly:
ssh ubuntu@VM_IP_ADDRESS
Or use built-in shell:
multipass shell vm1
File Sharing
Mount local directory to VM:
multipass mount /path/to/local/directory vm1:/path/in/vm
VM Management
- List VMs:
multipass list
- Start VM:
multipass start vm1
- Stop VM:
multipass stop vm1
- Delete VM:
multipass delete vm1
- Permanently remove:
multipass purge
Ansible: Automation for Your Lab
Ansible is my go-to tool for automating home lab setup and management. For a clean install, I recommend using uv to manage Python and Ansible dependencies—it keeps your environment tidy and repeatable.
To test your Ansible playbooks, use Multipass to spin up local VM. Set up SSH access for the root user using SSH keys only, and always disable password login for security.
If you want a lightweight web UI to manage and schedule your playbooks, try Semaphore. It’s easy to set up, supports cron-like scheduling, and uses a portable Bolt database.
Managing Python Environments with uv
uv is a modern Python package and environment manager that stands out for several reasons.
- Speed: uv is significantly faster than traditional tools like pip and virtualenv.
- Portability: uv is a single binary with no dependencies, making it easy to use across different systems.
- Container/Docker friendly: It's portability and single-binary design make it ideal for packaging Python environment inside containers, simplifying Docker builds and reducing image size.
Installing uv
curl -LsSf https://astral.sh/uv/install.sh | sh
Initializing Ansible Project with uv
uv init
uv add ansible
uv sync
Nomad
Install Nomad with hashi-up (Single Node)
hashi-up nomad install \
--ssh-target-addr 192.168.1.10 \
--ssh-target-user ubuntu \
--ssh-target-key ~/.ssh/id_ed25519 \
--server
Replace the IP address, username, and SSH key path with your environment details.
Enable Podman Task Driver & Raw exec plugin in Nomad
Make sure you have Podman installed. Edit the nomad configuration file (/etc/nomad.d/nomad.hcl) and add the following:
# generated with hashi-up
datacenter = "dc1"
data_dir = "/opt/nomad"
plugin_dir = "/opt/nomad/data/plugins"
server {
enabled = true
bootstrap_expect = 1
}
plugin "nomad-driver-podman" {
enabled = true
}
plugin "raw_exec" {
config {
enabled = true
}
}
client {
enabled = true
}
Restart Nomad to apply the changes.
sudo systemctl restart nomad
Check that the Podman and Raw exec driver is enabled:
nomad node status -self -short | grep Drivers
CSI Drivers = <none>
Drivers = exec,java,podman,qemu,raw_exec
Deploy Traefik with Nomad and Enable Nomad Service Discovery
Create a Nomad job file (e.g., traefik.nomad
) with the following content:
job "traefik" {
datacenters = ["dc1"]
type = "service"
group "traefik" {
count = 1
network {
port "http" {
static = 8080
}
port "admin" {
static = 8081
}
}
service {
name = "traefik-http"
provider = "nomad"
port = "http"
}
task "server" {
driver = "podman"
config {
image = "docker.io/traefik:v2.11.2"
ports = ["admin", "http"]
args = [
"--api.dashboard=true",
"--api.insecure=true", # Do not expose to the internet!
"--entrypoints.web.address=:${NOMAD_PORT_http}",
"--entrypoints.traefik.address=:${NOMAD_PORT_admin}",
"--providers.nomad=true",
"--providers.nomad.endpoint.address=http://${NOMAD_IP_http}:4646",
"--providers.nomad.exposedByDefault=false"
]
}
}
}
}
Deploy the job:
nomad job run traefik.nomad
Deploy a Demo Web Application (with Nomad Service Discovery and Traefik Routing):
Create a Nomad job file (e.g., demo-webapp.nomad
) with the following content:
job "demo-webapp" {
datacenters = ["dc1"]
group "demo" {
count = 3
network {
port "http" {
to = -1 # Dynamic port allocation
}
}
service {
name = "demo-webapp"
port = "http"
provider = "nomad"
tags = [
"traefik.enable=true",
"traefik.http.routers.demo-webapp-http.rule=Host(`demo-webapp-192-168-1-10.sslip.io`)",
"traefik.http.routers.demo-webapp-http.tls=false"
]
check {
type = "http"
path = "/"
interval = "2s"
timeout = "2s"
}
}
task "server" {
env {
PORT = "${NOMAD_PORT_http}"
NODEIP = "${NOMAD_IP_http}"
}
driver = "podman"
config {
image = "docker.io/hashicorp/demo-webapp-lb-guide"
ports = ["http"]
}
}
}
}
Deploy the job:
nomad job run demo-webapp.nomad
Once deployed, Traefik will automatically discover the demo-webapp service via the Nomad provider and route traffic to all running instances. You can test load balancing by sending HTTP requests to the configured hostname and port.
curl http://demo-webapp-192-168-1-10.sslip.io:8080
# Output example:
# Welcome! You are on node 192.168.1.10:20190
Repeat the request several times to see responses from different containers, indicating load balancing is working.
Tilt
Tilt is a handy tool for local development with Kubernetes. To setup a local Kubernetes cluster checkout microk8s - just make sure to enable the host-access and registry addon.
I keep an example Tiltfile project that serves as my cheatsheets whenever I need to spin up a new project. You can find it here.
Useful Resources
- Tilt your world
- Tilt for Kubernetes
- Revolutionizing CI with Argo Event Workflows & Tilt
- Local dev environment with Tilt
Mutagen
Grab the latest binary from here
Make sure you've already set up your SSH config for your remote machine. Here's an example from my ~/.ssh/config
:
Host devbox
HostName 192.168.1.100
User jimbo
Sync files from remote to local:
mutagen sync create \
--name=music-localdev ./localdev \
jimbo@devbox:~/clones/music/localdev --ignore-vcs
Now you can open the local folder with any editor you like.
Port forwarding:
mutagen forward create --name=mysql tcp:localhost:3306 devbox:tcp:localhost:3306
This lets you connect to the remote MySQL server using your local MySQL client.
Using it along with WSL
My quick tips for WSL:
- Treat WSL and Windows as separate systems
- Access WSL files from Windows through the
/wsl
network drive - Avoid using the
/mnt
path in WSL for Windows files - it's slow! Use git or Mutagen instead for better performance
Useful Resources
Kubernetes (mikrok8s)
Prefer ubuntu based host for mikrok8s, you can use lima-vm or wsl2 distro. you can follow the instructions here for installing mikrok8s.
kubectl config
Make sure ~/.kube
exists
mkdir -p ~/.kube
Create the config file
microk8s config > ~/.kube/config
Execute cluster-info
to verify the connection
kubectl cluster-info
Microk8s addon
Enable host-access addon for convenient way to access the host from inside the cluster.
microk8s enable host-access
Since tilt need a local registry to push the images, you can enable the local registry addon.
microk8s enable registry
later if you want to free some spaces from the registry, you can run the following command:
microk8s disable registry
microk8s disable storage:destroy-storage
microk8s enable registry
Useful Resources
Flux
Bootstrap flux with existing git repository, image-reflector and image-automation controllers enabled for image update automation.
flux bootstrap git --url=ssh://git@<YOUR_GIT_REPOSITORY_URL> \
--private-key-file=$HOME/.ssh/id_ed25519 \
--branch=main \
--path=clusters/homelab \
--components-extra image-reflector-controller,image-automation-controller
Upgrading Flux
Update your flux cli into the latest version:
flux install \
--components-extra="image-reflector-controller,image-automation-controller" \
--export > ./clusters/homelab/flux-system/gotk-components.yaml