Workstation Setup tutorial series — Part 2: Set up the basics
WSL2, Docker Desktop, VS Code with the right extensions, Node, PHP, GitHub auth (the gh CLI over HTTPS), the OpenSpec CLI, and the Playwright browser. The longest part of the series — most of the time goes into downloads while you do something else. After this, you have a working dev workstation, just without Claude Code wired up yet.
This is the part with the most install commands. WSL2, Docker Desktop, VS Code with the right extensions, the language runtimes (Node, PHP, Composer), the CLIs (gh, OpenSpec), and the Playwright Chromium binary. By the end of Part 2 you'll have a working dev workstation — just without Claude Code wired up yet. That's Part 3.
A note on macOS and Linux
This part assumes Windows 11. If you're on macOS or Linux:
- macOS — skip step 1 (WSL2) entirely. Replace
apt installwithbrew installin the rest. Install Docker Desktop natively. The order and the verification commands stay the same. - Linux native — skip step 1 (WSL2). Install Docker Engine natively (not Docker Desktop). The order stays the same.
Our canonical workstation-setup.md documents the Windows + WSL2 path; the macOS and Linux variants above are the translation most teams use. If you hit something that doesn't translate cleanly, send a note via Email us at the bottom.
Step 1: install WSL2 (Windows only)
Open PowerShell as Administrator — right-click the Start button, pick "Terminal (Admin)" or "PowerShell (Admin)" — then:
wsl --install -d Ubuntu-24.04
Restart your machine when prompted. After the reboot, Ubuntu finishes its first-time setup and asks you to create a Linux username and password. Pick a username you'll remember; the password is the sudo password for your Ubuntu install.
Verify in PowerShell:
wsl --version
wsl -l -v
You should see Ubuntu-24.04 running on WSL version 2.
No reboot? Restart Ubuntu via
wsl --shutdownin PowerShell, then re-open the Ubuntu app. That clears nine out of ten "it just won't start" issues.
Step 2: install Docker Desktop
Download Docker Desktop for Windows from docker.com/products/docker-desktop and install it. After installation:
- Open Docker Desktop > Settings > Resources > WSL Integration.
- Enable integration with your Ubuntu-24.04 distro.
- Click Apply & Restart.
Open your Ubuntu terminal (Start menu → Ubuntu) and verify:
docker --version
docker compose version
Both should return a version string. If docker --version reports "command not found" inside WSL, the WSL integration didn't apply — quit Docker Desktop fully (right-click tray icon → Quit), then start it again.
Step 3: install VS Code
Download Visual Studio Code from code.visualstudio.com and install it on Windows (not inside WSL).
Then connect it to WSL:
- Open VS Code.
Ctrl+Shift+Xto open Extensions, install WSL (ms-vscode-remote.remote-wsl) on the Windows side. This one really must be on Windows, not WSL.Ctrl+Shift+P> "WSL: Connect to WSL".- Pick Ubuntu-24.04.
VS Code installs its server component inside WSL automatically. From now on, every VS Code terminal lives inside WSL.
Step 4: install VS Code extensions
Inside the WSL-connected VS Code window, install these. Easiest is from the WSL terminal:
code --install-extension anthropic.claude-code
code --install-extension ms-azuretools.vscode-docker
code --install-extension bmewburn.vscode-intelephense
code --install-extension vue.volar
code --install-extension dbaeumer.vscode-eslint
code --install-extension ms-python.python
code --install-extension eamodio.gitlens
code --install-extension redhat.vscode-yaml
code --install-extension github.vscode-github-actions
That covers the required + recommended set for a Conduction project. See workstation-setup.md for the optional ones.
Step 5: install Node (via nvm)
All Node work happens inside WSL, never on the Windows side. Use nvm so you can switch versions per project without sudo:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install 20
nvm alias default 20
Verify:
node --version # v20.x
npm --version # 10.x
Why Node 20, not the latest? Docusaurus 3.x and the OpenSpec CLI both want Node 20+, and our canonical workstation-setup.md installs Node 20 specifically. Stick to 20 LTS until you have a reason not to.
Step 6: install PHP 8.1+ and Composer
sudo apt update
sudo apt install -y php8.1-cli php8.1-xml php8.1-mbstring php8.1-curl php8.1-zip unzip
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
Verify:
php --version # 8.1+
composer --version # 2.x
Use phpcs v3, not v4, when a project asks for it. The CI is pinned to v3 and v4 breaks the shared
phpcs.xmlconfig. If acomposer installlater tries to upgrade, override withcomposer require --dev "squizlabs/php_codesniffer:^3.9".
Step 7: GitHub auth (gh CLI over HTTPS)
Conduction's platform is GitHub, under the ConductionNL org. The 2026-05-29 move to Codeberg was reversed — directive 2026-07-17, executed 2026-07-23 — so the GitHub CLI (gh) is the one you actually need. It does two jobs: PRs, issues and labels through the GitHub API, and the credentials for every git clone/push/pull over HTTPS. No SSH key, no passphrase, no keychain.
7a. Install gh and log in
sudo apt install -y gh
gh auth login
Answer the prompts like this:
- Account →
GitHub.com - Preferred protocol for Git operations →
HTTPS - Authenticate Git with your GitHub credentials? →
Yes - How would you like to authenticate →
Login with a web browser
gh prints a one-time code and opens the browser. If no browser opens from WSL, go to github.com/login/device yourself and paste the code there. If your work GitHub uses SSO, make sure your default browser is Edge or Chrome — Firefox SSO callbacks sometimes don't complete cleanly.
Answered No to the git question, or installed gh earlier? Wire it into git afterwards:
gh auth setup-git
That registers gh as git's credential helper for github.com, so git push uses the same token as gh — nothing to type, nothing to unlock after a reboot.
7b. Verify
gh auth status
# github.com
# ✓ Logged in to github.com account <YourGitHubUsername> (keyring)
# - Git operations protocol: https
Both lines matter: the first says gh is authenticated, the second says git goes over HTTPS through it. Clone ConductionNL repos with the https://github.com/ConductionNL/<repo>.git URL — the rest of this series does the same.
An SSH key is optional. Set one up only if you deliberately want
[email protected]:remotes —gh auth loginoffers to generate and upload one when you pick SSH as the protocol. A key with a passphrase needs an agent such askeychain, which costs a passphrase prompt once per WSL boot. HTTPS throughghhas none of that, and it's what this series assumes.
tea are not part of ConductionNL workAn SSH key for Codeberg plus the tea CLI is only needed for a non-Conduction repo that genuinely lives on a Forgejo host (Codeberg, or a customer's self-hosted Forgejo/Gitea). For that case, the Codeberg Authentication Setup guide in the .github repo has the full walkthrough.
Followed an earlier version of this step? Then every new terminal asks for the passphrase of ~/.ssh/id_ed25519_codeberg. Open ~/.bashrc and comment out this line:
# eval $(keychain --eval --quiet --agents ssh ~/.ssh/id_ed25519_codeberg)
Need the key again later? Load it once for the WSL session, or restore the line:
keychain ~/.ssh/id_ed25519_codeberg
Step 8: install the OpenSpec CLI
Used by all /opsx-* skills for spec-driven development:
npm install -g @fission-ai/openspec
openspec --version
Do NOT run
openspec initinside a Conduction project — those projects already have a customisedopenspec/directory. Runninginitoverwrites them.
Step 9: install the Playwright Chromium binary
The Playwright MCP browsers (used by our testing skills) need Chromium installed once on this machine:
npx playwright install chromium
The download is ~300 MB. Run it on a stable connection — not on the train.
Step 10: verify everything
A clean dev workstation passes this checklist with no errors:
node --version # v20.x
npm --version # 10.x
php --version # 8.1+
composer --version # 2.x
docker --version # 24+
docker compose version # 2.x
gh --version # 2.x
gh auth status # "Logged in to github.com account <you>" + "Git operations protocol: https"
openspec --version # 1.x
npx playwright --version # 1.x
If every command returns a real version and gh auth status shows you logged in with https as the git protocol, you're done with Part 2. Otherwise, jump to Troubleshooting below.
Troubleshooting
WSL2 won't install: 'WSL is not supported on this version of Windows'You're probably on Windows 10 without the latest cumulative update, or on Home edition without virtualisation enabled. Update Windows, then enable Virtual Machine Platform and Windows Subsystem for Linux via Turn Windows features on or off. Reboot and retry.
docker command not found inside WSL after Docker Desktop installThe WSL integration didn't take. Open Docker Desktop > Settings > Resources > WSL Integration, toggle Ubuntu-24.04 off and on again, click Apply & Restart. Then re-open the Ubuntu terminal.
composer install fails with 'vendor/ is owned by root'Happens after the first Docker run inside a project — Docker created vendor/ as root. Two fixes: (1) sudo chown -R $USER:$USER vendor/ and rerun, or (2) install phpcs globally with composer global require "squizlabs/php_codesniffer:^3.9" and skip composer install for now.
gh auth login opens the wrong browser and SSO never completesIf your work GitHub uses SSO and Firefox is the default browser, the callback sometimes doesn't come back cleanly. Set Edge or Chrome as the default browser and rerun gh auth login --web.
git push asks for a username and passwordGit isn't using gh as its credential helper — you answered No to Authenticate Git with your GitHub credentials?, or you're logged out. Run gh auth setup-git (and gh auth login first if gh auth status says you're not logged in). Then retry.
git clone [email protected]:... returns 'Permission denied (publickey)'That's an SSH URL, and with the HTTPS setup from Step 7 you have no SSH key on GitHub. Use the HTTPS URL instead (https://github.com/ConductionNL/<repo>.git). For a clone you already have, switch it with git remote set-url origin https://github.com/ConductionNL/<repo>.git.
Every new terminal asks for the passphrase of id_ed25519_codebergLeft over from an earlier version of Step 7, which set up Codeberg. Comment out the eval $(keychain ... id_ed25519_codeberg) line in ~/.bashrc and open a fresh terminal. See the note at the end of Step 7.
npx playwright install chromium hangs or times outThe download is ~300 MB. On a slow connection it can look stuck. Wait it out, or rerun on a faster network. If it really refuses, set HTTPS_PROXY and try again from a network where the proxy resolves.
Test yourself
Five short questions to check that this part landed. Stuck? Click Hint. Curious about the answer? Click Answer.
1. Why do we install Node, PHP and the rest inside WSL instead of on the Windows side?
Hint
Think about which file system the tooling reads and writes, and which shell scripts later steps will run.
Answer
Because every later step — Composer, the OpenSpec CLI, the gh CLI, the global Claude safety hooks (Part 3) — assumes a POSIX shell and Linux file paths.
Installing Node or PHP on Windows leads to two trips through path translation (\\wsl$\... ↔ C:\Users\...), inconsistent line endings, and bash hooks that just don't run. The rule of thumb: anything you'll run from a shell goes inside WSL; editor and Docker Desktop run on Windows.
2. The Docker command works in Windows PowerShell but not inside WSL. What's the most likely cause?
Hint
Docker Desktop runs on Windows. It needs an explicit step to be reachable from WSL.
Answer
The WSL Integration wasn't enabled (or didn't apply) for your Ubuntu distro. Open Docker Desktop > Settings > Resources > WSL Integration, toggle your Ubuntu distro off and on, click Apply & Restart. Then re-open the Ubuntu terminal.
If that still doesn't help, quit Docker Desktop fully (right-click tray icon → Quit) and start it again. That clears the last 10% of cases.
3. Why do we install the WSL extension for VS Code on the Windows side, and the rest on the WSL side?
Hint
The WSL extension does one specific thing the others don't.
Answer
The WSL extension is what connects VS Code on Windows to a remote VS Code Server inside WSL — that step has to live on Windows. Once that connection is up, the other extensions (Claude Code, Volar, ESLint, Intelephense, Docker) need access to your code, your node_modules, and your shell — so they install on the WSL side, against the same file system as your project.
Installing language extensions on Windows when the code lives in WSL gives you broken IntelliSense, slow file watchers, and confusing path errors. Keep the rule simple: WSL extension on Windows, everything else inside WSL.
4. git push to a ConductionNL repo fails. In one clone you get "Permission denied (publickey)"; in another, git asks for a username and password. What causes each, and which single command confirms that auth works?
Hint
One failure is about the clone's remote URL. The other is about whether git knows to ask gh for credentials. Two separate failure modes; two separate checks.
Answer
The two failure modes are:
- "Permission denied (publickey)" — the remote is an SSH URL. Diagnose:
git remote -vshows[email protected]:ConductionNL/.... With the HTTPS setup from Step 7 there is no SSH key on your GitHub account, so SSH has nothing to offer. Fix:git remote set-url origin https://github.com/ConductionNL/<repo>.git. - A username/password prompt — git isn't using
ghas its credential helper. You answered No to Authenticate Git with your GitHub credentials?, or you're logged out. Fix:gh auth setup-git(aftergh auth loginif needed).
In either case, after fixing, gh auth status should report "Logged in to github.com account <your username>" with "Git operations protocol: https" — the surest single confirmation that auth works.
5. After the first docker compose up -d of a Conduction project, composer install on the host suddenly fails with Permission denied on vendor/. What happened, why is it specifically the first Docker run, and what's the fastest fix?
Hint
Docker containers run as root by default. When a container writes into a bind-mounted directory, the files inherit that ownership on the host.
Answer
When Docker first starts a Conduction project, the PHP container writes into the bind-mounted vendor/ directory — but the container runs as root, so the resulting files are owned by root:root on the host file system. Your host user ($USER) can't write to them, and composer install from the host fails.
Why specifically the first run: before that, vendor/ either didn't exist or was clean. Subsequent runs reuse the already-root-owned files, so the error persists until you fix the ownership.
Fastest fix:
sudo chown -R $USER:$USER vendor/
composer install
Alternative if you don't want to use sudo: install phpcs globally (composer global require "squizlabs/php_codesniffer:^3.9") and skip the local composer install for the moment. The CI still uses the project's composer.json, so this only affects local tooling.
Next step
With the runtimes installed, it's time to wire up Claude Code itself — and, just as importantly, the global safety hooks.