S001 · Dev Environment — Linux/WSL, Terminal, VS Code
The 90 minutes that saves you 90 hours. Real environment, real editor, real terminal — no ‘works on my machine’ for the next six months.
🎯 Build a dev environment you can rebuild from a bash script in under 15 minutes on any machine.
Why this session exists
Every senior engineer you'll ever meet has a scar tissue collection of "it worked on my laptop" stories. Almost all of them trace back to the same root cause: an environment set up by clicking around instead of by design. The good news is you can skip that scar collection entirely with a boring 90 minutes of setup. That's what this session is.
By the end you will:
- Explain WSL, virtual environments, and dotfiles to a non-technical friend in under a minute each.
- Reproduce your entire dev environment on a fresh machine in <15 min using a script you own.
- Diagnose the top-3 environment bugs (CRLF, cross-filesystem I/O, missing venv) in under 60 seconds.
- Boot VS Code connected to a real Linux shell, install Python via uv, run a script, and freeze deps.
Prerequisites
None. This is session one of one hundred and thirty — you don't need to know anything except how to type.
(a) Intuition · 5 min
You've just moved into a new professional kitchen. Before you cook anything, you sharpen the knives, arrange the spices so your hand can find cumin without looking, and check that the stove actually turns on. That's 90 boring minutes.
Then for the next six months, you cook 300 meals without a single "where did I put the salt?" moment. The 90 minutes paid for itself before dinner two.
A dev environment is the same. You install the shell, the editor, the package manager, and the language runtime once, on purpose, with a script you keep in Git. Everything after that is coding.
Skip this step and you'll spend the next six months fighting Windows line endings, wrong Python versions, and "why is my hot-reload 8 seconds?" mysteries instead of learning ML.
The three problems it solves
- Reproducibility — a fresh machine (or a coworker) can rebuild what you have from a script, not from memory.
- Isolation — this project's Python 3.12 + pandas 2.2 doesn't collide with next project's Python 3.9 + pandas 1.5.
- Portability — Linux is the operating system every server runs. Your local machine should speak the same language so a bug on your laptop is a bug in production.
WSL (Windows Subsystem for Linux) exists because problem three used to require dual-booting or spinning up a VM every time. WSL 2 ships a real Linux kernel that runs quietly next to Windows, shares clipboard and files, and lets VS Code on Windows edit files inside Linux transparently. macOS users already have a Unix shell built in — you get the same benefit for free.
A quick history so you know why the world looks like this
- 1969Unix at Bell LabsKen Thompson & Dennis Ritchie build Unix. The shell (sh) ships two years later.
- 1991Linux kernel · Linus TorvaldsA free, open Unix-alike. Servers slowly adopt it; by 2010 it runs the internet.
- 2015VS Code releasedMicrosoft ships a free, cross-platform Electron editor. Kills Atom in two years.
- 2019WSL 2 goes GAReal Linux kernel inside Windows. Microsoft finally admits the world runs on Unix.
- 2024uv released · AstralRust-based Python installer + venv + resolver. 10× faster than pip. Replaces four legacy tools.
(b) Visual walkthrough · 15 min
The stack on Windows (delete WSL for macOS/Linux)
The install path — one time, ever
Run in PowerShell as admin. Installs Ubuntu + WSL 2 kernel; reboots.
This is your Linux identity. Independent of your Windows login.
sudo apt update && sudo apt upgrade -y — refresh package index.
Then add the ‘WSL’ extension from the marketplace.
Status bar should show ‘WSL: Ubuntu’ — you're connected.
Directory layout you'll use for six months
Rules to internalise:
Code in Linux filesystem
- ~/projects/… (native ext4)
- One folder per session
- A venv per folder
- requirements.txt in Git
- dotfiles in a separate repo
Code on Windows drive
- /mnt/c/Users/… (10-100× slower)
- One big Documents/Code folder
- Global pip install for everything
- ‘I'll remember what I installed’
- config only in your head
The mental model to hold
"My dev environment is set up — Python is installed, VS Code works, I ran pip install and it worked. Environment setup is a one-time chore I can now forget about."
An environment is not a state you reach, it's a specification you keep. The only environment that matters is the one you can reproduce on a machine you have never touched, from a file in version control, without you present.
Because on day one it genuinely is one-time. You install once, it works, nothing breaks — for weeks. The cost is deferred, not absent. It arrives the day a teammate clones your repo and gets ModuleNotFoundError, or CI fails on a version you upgraded locally six months ago and forgot, or a notebook that trains fine on your laptop dies in the cluster. Every one of those is the same bug: the environment lived in your machine's history instead of in a file.
The honest test of any environment — run it right now:
# 1. What is actually installed, and where did it come from?
which -a python python3 pip
python -c 'import sys; print(sys.executable, sys.version)'
pip list --not-required # top-level packages only
# 2. The real test: can a stranger rebuild it?
python -m venv /tmp/fresh && /tmp/fresh/bin/pip install -r requirements.txt
# if that fails, your environment is undocumented, not 'set up'Why do virtual environments exist at all? Language ecosystems could have solved this with a smarter global installer — why did every serious one converge on per-project isolation instead?
- 1A program imports a library by name, not by version.
import requestssays nothing about which requests.forced by · source code is written once and must keep working as the world moves - 2So the runtime must resolve that name at import time by searching an ordered list of directories (
sys.path) and taking the first match.forced by · name resolution needs a deterministic winner, and "first on the path" is the cheapest rule - 3If there is exactly one shared site-packages directory, then there is exactly one version of each library visible to every program on the machine.forced by · one directory can only hold one
requests/folder - 4But two projects on the same machine legitimately need different versions — project A pinned to an old API, project B needing a new feature. This is not a mistake; it is the normal steady state of any codebase older than a year.forced by · libraries make breaking changes on their own schedule, not yours
- 5Therefore the conflict is unsolvable by any global installer. No dependency resolver can satisfy A and B simultaneously in one namespace — it is arithmetically impossible, not merely hard.forced by · the constraint set is genuinely unsatisfiable, so no algorithm helps
Therefore the only fix is to stop sharing the namespace: give each project its own site-packages and its own front-of-path entry. That is a virtualenv — not a sandbox, not a container, just a directory prepended to sys.path.
And note what this predicts: activation must be a per-shell, per-process property (it only edits PATH), so a cron job or systemd unit that doesn't inherit your shell will silently use the system Python. It also predicts the fix — invoke /path/to/venv/bin/python directly and never rely on activation in automation.
Treat your laptop like a CPU cache in front of your repository. Anything valuable that exists only in the cache — an installed package, an exported variable, a hand-edited config, a one-off pip install — is not saved. It is a cache line that will be evicted, by a new laptop, a new teammate, or a CI runner.
The question for every setup action is not "does it work now?" but "which file recorded this?" If the answer is "none", you did not configure anything; you damaged reproducibility and got lucky.
- Every install must be written down:
requirements.txt/pyproject.toml/environment.yml. An undeclared dependency is a future outage with a delay fuse. - Pin versions for anything that ships or trains. Floating versions mean your build depends on the date it ran.
- Secrets are the one thing that must not be in the repo — so they get an explicit mechanism (env vars, key vault), plus a committed
.env.examplenaming them. - The definition of done for setup is a clean-machine rebuild, not a working laptop.
Fire this model the moment you see: "works on my machine" · a pip install typed into a terminal and not into a file · a notebook that needs a magic cell to run · onboarding docs written in prose instead of a script · a CI failure that reproduces nowhere locally.
How much isolation do you buy for a project — plain virtualenv, conda, or a container?
sys.path)Senior engineers pick the lightest isolation that makes the rebuild reproducible, and escalate only when a real failure proves it insufficient. Start with venv; move to conda when a compiled dependency bites; move to containers when the deployment target stops matching your laptop.
The layers compose — a container that internally installs from a lockfile is common and correct. What is not correct is skipping the lockfile because you have a container: the image is then reproducible only until its base tag moves.
(c) Hands-on · 25 min
Run this end-to-end on a fresh Ubuntu / WSL / macOS terminal. Copy the whole thing into a script called setup.sh, chmod +x setup.sh, run it. It creates your first project, installs Python via uv, and prints a smoke test.
What each block does
Anatomy of the script
Add this line to the top of hello.py:
import requests
print("requests version:", requests.__version__)Run python hello.py and watch it fail with ModuleNotFoundError: No module named 'requests'. That's a good failure — it proves the venv is isolated from your system Python (which almost certainly has requests installed globally).
Bonus — a first dotfile
Every senior engineer keeps a ~/.bashrc (or ~/.zshrc) they've evolved over years. Yours starts today. Add these three lines:
# ~/.bashrc additions — first version, will grow
alias ll='ls -alF'
alias ..='cd ..'
export PATH="$HOME/.local/bin:$PATH" # so uv is always foundThen source ~/.bashrc (or open a new terminal). ll now lists files with details; .. jumps up a directory. Session S004 (dotfiles) will grow this into a full config you can rebuild anywhere in 30 seconds.
(d) Production reality · 15 min
Every senior engineer has a horror story that starts with "it worked on my laptop." The three below are the ones you personally will hit in the first month.
A Next.js dev on Windows cloned a repo to /mnt/c/work/ and reported hot-reload took 8 seconds per file save. He filed it as a WSL bug.
It wasn't a bug. Every file read had to cross the WSL↔Windows filesystem boundary — a 9P protocol translation on each syscall.
~/work/ (native ext4 inside Linux) dropped reload to 200 ms. He then complained he could still access ~ from Windows via \\wsl$\Ubuntu\home\…, so nothing was lost.~/projects, always. If you must edit from Windows Explorer, use the \\wsl$\ path.bash: /deploy.sh: /bin/bash^M: bad interpreter. That ^M is a carriage return Windows added to every line.Emergency: run dos2unix deploy.sh on the server. Permanent: add to every repo:
# .gitattributes
* text=auto eol=lf
*.sh text eol=lfAnd to every Windows machine: git config --global core.autocrlf input.
pandas, run your script, get ModuleNotFoundError: pandas. You reinstall. Same error. You reboot. Same error. You cry.direnv (auto-activates venv when you cd into the folder) or uv run script.py (runs inside the project's venv without needing activation at all).direnv or uv run by Session S005 at the latest.How top teams handle this
Google, Microsoft, and Netflix ship devcontainers — a JSON file in the repo (.devcontainer/devcontainer.json) that describes the exact Ubuntu image, tool versions, and VS Code extensions. New engineer clones the repo, VS Code prompts "Reopen in container?", and 3 minutes later they have the exact environment everyone else has. Zero "works on my machine" tickets. Session S056 (Docker) is the foundation for this.
Where this shows up in the rest of the plan
(e) Recall + stretch · 10 min
Explain-out-loud test
If you can't teach these three to a friend without notes, redo the session:
- What is a dev environment? (one sentence, no jargon)
- What is a virtual environment, and why? (one concrete example)
- What is one thing you should never do in a dev environment? (and why)
What comes next
Hub: The 6-Month Learning Plan
Part of a 130-session evergreen learning series. Session structure: intuition → visual → hands-on → production war stories → recall. Duration: 90 minutes.