Install Mesopod on Windows (WSL2)¶
Mesopod runs on a Windows 11 PC inside WSL2, Windows' built-in way to run Linux. The console gives you one PowerShell line. It sets WSL up as a server, installs Mesopod in a Linux distro of its own, and keeps that distro running across restarts.
WSL2 is built as a developer's shell rather than a server, so most of this page explains what the line changes to make it one: where each change lives, how to check it, and how to put it back. After that come the parts of running a server that are particular to Windows — memory, storage, VPN clients, remote access — and the problems you are likely to meet. What works the same on every machine, such as making app names resolve at home, is in install.md.
Commands here use the production console, console.mesopod.cloud. On a
non-production Mesopod, use the origin from your own install command instead.
What you need¶
- Windows 11, version 22H2 or newer (build 22621 or later), x64 or arm64. The networking mode the cluster depends on needs 22H2; the installer stops on an older build and says so.
- An administrator account on the PC. The installer asks Windows for permission once, and the rest runs as you.
- WSL 2.4.4 or newer, which you don't have to install yourself: the installer installs WSL on a PC that has none and updates an older one. (Mirrored networking itself needs WSL 2.0; the installer needs 2.4.4 to create its distro by name.)
- Outbound HTTPS. The PC must reach
console.mesopod.cloud:443andbridge.mesopod.cloud:443, and during the install also WSL's own downloads andget.k3s.io. No port forwarding on your router and no public IP: nothing on the internet connects in. - Docker Desktop, if you have it, can stay. Turn its Kubernetes off if it is on — Mesopod runs its own — and make sure nothing it runs is published on port 80 or 443, which the cluster needs.
The PC should be one you leave on. Your apps are only up while it is.
Install: one line¶
In the console, open Clusters and use Add cluster (install.md, step 1). The install command has a Windows tab:
$env:MESOPOD_CLAIM='mpc_…'; irm https://console.mesopod.cloud/api/v1/install.ps1 | iex
Paste it into an ordinary PowerShell window — not an administrator one, and not a Linux shell. Windows asks for permission once; the install continues in a new window, which waits for Enter at the end so you can read it. The steps it takes are listed in install.md; the rest of this page goes through each one.
On a PC where WSL isn't set up yet, the first run turns it on and stops:
Restart required. Restart Windows, then run the install command again.
The install command is still valid.
Restart, paste the same line, and it carries on. Nothing was spent: the claim is exchanged later, inside the distro, and it lasts 24 hours.
A first install that gets all the way through ends like this, with your own address and paths:
+ outpost installed and dialling home
> return to the console — the cluster row walks: waiting for your server -> setting up -> ready
> WSL2: the Windows installer handles the startup task, the Hyper-V firewall rule and hostAddressLoopback
> the console should show this cluster's DNS target as 192.168.1.40 (this PC's address) — if it shows another address, set the DNS target to 192.168.1.40 from the cluster's row
+ firewall rule 'Mesopod' enabled (TCP 80, 443 inbound; applies to all WSL distros)
> registering startup task 'Mesopod WSL'...
> testing startup task...
+ startup task 'Mesopod WSL' registered
+ done. Return to the console to watch the cluster come online.
installed: WSL distro 'mesopod', startup task 'Mesopod WSL', firewall rule 'Mesopod' (TCP 80, 443)
repair: run the install command again
uninstall: & 'C:\Users\you\AppData\Local\Mesopod\install.ps1' -Uninstall
if the cluster is offline after a restart: & 'C:\Users\you\AppData\Local\Mesopod\install.ps1' -StartupTaskPassword
Back in the console, the row moves to Setting up and then
Provisioned, as on any machine. Then check the one thing the install asks
you to: the cluster's row shows dns → and an address, and it should be the
one the install printed, the PC's own address on your network. If it isn't,
use Edit beside it; VPN clients explains the usual cause.
What a server needs from WSL2¶
WSL2 runs Linux in a lightweight virtual machine that starts when you open a terminal in a distro and stops shortly after the last one closes. By default it runs systemd only in a distro that asks for it, gives Linux an address that nothing else on your network can reach, and starts nothing when the PC boots. The installer changes six things to make it a server:
| What | Why the cluster needs it | Where it lives |
|---|---|---|
| systemd | k3s, the Kubernetes Mesopod installs, is a systemd service, and WSL starts distros without systemd unless told to | /etc/wsl.conf in the distro |
| Mirrored networking | Under WSL's default networking, the distro's address is private to the PC | %UserProfile%\.wslconfig |
| The port hold | Windows passes a port to Linux only while a Linux process has bound it | mesopod-wsl-ports.socket in the distro |
| The Hyper-V firewall rule | The firewall in front of WSL blocks inbound connections unless a rule allows them | rule Mesopod |
| Host loopback | Without it, the PC can't reach the apps at its own address | %UserProfile%\.wslconfig |
| The startup task | WSL doesn't start a distro at boot, and stops one nobody is using | task Mesopod WSL |
A quick check¶
Run these in an administrator PowerShell window (search the Start menu for
PowerShell, then choose Run as administrator). Run the first line before
the others: the wsl -d mesopod lines start the distro if it was stopped.
wsl --list --running
wsl -d mesopod systemctl is-system-running
wsl -d mesopod wslinfo --networking-mode
wsl -d mesopod systemctl is-active mesopod-wsl-ports.socket k3s
Get-NetFirewallHyperVRule -Name Mesopod
Get-ScheduledTask -TaskName 'Mesopod WSL'
A healthy PC answers: mesopod among the running distros; running (or
degraded, which still means systemd booted); mirrored; active twice;
the rule with Enabled True; the task with State Running. The sections
below say what each answer means and how to fix a wrong one.
systemd¶
k3s runs as a systemd service, and WSL starts a distro without systemd unless
its /etc/wsl.conf asks for it. The installer merges these lines into the
mesopod distro's /etc/wsl.conf, keeping anything else in the file:
[boot]
systemd=true
[user]
default=root
The [user] setting makes root the distro's user: nobody logs in to this
distro, and wsl -d mesopod opens a root shell. The installer also turns off
Ubuntu's first-run wizard, which would otherwise ask the first person to open
a shell there to create a user.
Check: wsl -d mesopod systemctl is-system-running prints running or
degraded. offline means something other than systemd started the distro:
run the installer again, which merges the
lines back and restarts WSL.
Mirrored networking¶
WSL's default networking puts Linux behind NAT: the distro gets a private
address, which changes when WSL restarts and which no other device can reach.
Mirrored mode instead gives the distro the PC's own network interfaces and
addresses, so your apps answer at the PC's address on your network — the
address the console points your app names at. The installer adds this to
%UserProfile%\.wslconfig, WSL's settings file, keeping every other line:
[wsl2]
networkingMode=mirrored
[experimental]
hostAddressLoopback=true
The second setting is host loopback. .wslconfig applies
to every WSL distro on the PC, not only mesopod, so your other distros get
mirrored networking too. Removing Mesopod leaves the file as it is.
Check: wsl -d mesopod wslinfo --networking-mode prints mirrored. If
it prints nat, the setting is missing or WSL hasn't restarted since it was
added: run the installer again.
The port hold¶
In mirrored mode, Windows hands an incoming connection to Linux only if a
Linux process has bound that port. k3s publishes your apps on ports 80 and
443 through packet-rewriting rules with no process bound behind them, so
without help Windows never passes those ports on, and the apps answer inside
the distro and nowhere else. The Linux half of the install adds a systemd
socket unit, mesopod-wsl-ports.socket, that binds both ports. It exists
only to claim them: real connections are rewritten to Traefik, k3s's router,
before the socket ever sees them.
Mirrored mode shares one set of ports between Windows and Linux, so the hold
fails if anything already listens on 80 or 443, in the distro or on Windows.
The install then stops with could not hold ports 80/443 in this distro,
naming the program when it can. On the Windows side,
netstat -ano | findstr ":443 " finds it (and ":80 " for the other port);
the last column is its process ID, which Get-Process -Id <PID> names.
Check: wsl -d mesopod systemctl is-active mesopod-wsl-ports.socket
prints active. To restore it,
run the installer again.
The Hyper-V firewall rule¶
Mirrored mode puts the distro behind the Hyper-V firewall, the firewall
Windows keeps in front of WSL, which blocks inbound connections unless a rule
allows them. The installer adds one rule, Mesopod (shown as "Mesopod
80/443"), allowing inbound TCP on ports 80 and 443. It creates the rule
disabled and enables it only once the Linux install has succeeded, so the
ports never open before Mesopod holds them.
Every WSL distro on the PC runs in the same virtual machine, and the rule
applies to that machine: a service another distro runs on port 80 or 443
becomes reachable from your network too (wsl -l -v lists your distros).
Microsoft's documentation also shows how to allow every inbound connection to
WSL; the installer opens only the two ports.
Check (administrator PowerShell):
Get-NetFirewallHyperVRule -Name Mesopod shows the rule with Enabled
True and Action Allow. If it is missing, changed or disabled,
run the installer again: it corrects a rule
of that name in place rather than deleting it. To turn it back on by hand:
Enable-NetFirewallHyperVRule -Name Mesopod.
Host loopback¶
In mirrored mode, a connection from Windows to one of the PC's own addresses
reaches Linux only when hostAddressLoopback=true is set under
[experimental] in .wslconfig, and Microsoft's default is off. Your app
names resolve to the PC's address, so without it every app opens from your
phone but not in a browser on the PC itself. The installer sets it, and
makes sure the Hyper-V firewall's own loopback setting for WSL is on (WSL
turns that one on by default).
Check: the [experimental] lines are in .wslconfig, and in an
administrator PowerShell this shows LoopbackEnabled True:
Get-NetFirewallHyperVVMSetting -PolicyStore ActiveStore -Name '{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}'
After adding the lines to .wslconfig yourself, run wsl --shutdown, then
start the cluster again.
The startup task¶
WSL doesn't start a distro when Windows boots, and stops one shortly after
the last terminal in it closes. Left alone, every restart — including the
ones Windows Update makes — would leave the PC on and the cluster off. So the
installer registers a Task Scheduler task, Mesopod WSL, that runs at
startup and holds the distro open with a process that never exits:
wsl.exe -d mesopod -u root --exec sleep infinity
The task runs as you, before anyone logs on, without storing your password: in Task Scheduler it reads "Run whether user is logged on or not", with "Do not store password" ticked. It has no time limit (Task Scheduler's default would stop it after three days). Opening and closing terminals in the distro doesn't affect it.
The installer tests the task before it finishes: it stops the distro, starts
the task, and waits up to 30 seconds for the distro to run again. On some PCs
Task Scheduler can't start WSL without a stored password. The installer then
asks for your Windows password and registers the task with it — Task
Scheduler keeps the password, Mesopod doesn't — and prints
startup task 'Mesopod WSL' registered (with stored password).
Sleep is not a restart: when the PC wakes, the distro is still running and nothing needs to happen. Your apps are unreachable while it sleeps, though, so if they should be up around the clock, set Windows not to sleep.
Check: Get-ScheduledTask -TaskName 'Mesopod WSL' shows State
Running, and wsl --list --running lists mesopod. If mesopod isn't
running after a restart even though the task is registered, Task Scheduler
most likely can't start WSL without your password on this PC. Register the
task with the password, in a new PowerShell window:
& "$env:LOCALAPPDATA\Mesopod\install.ps1" -StartupTaskPassword
Then restart Windows to confirm it, or start the cluster now.
Starting the cluster without a restart¶
The task runs only at startup. Anything that stops WSL while Windows keeps
running ends its hold, and the cluster then stays off until the next restart:
wsl --shutdown (which stops every distro), or a re-run of the installer
that printed restarting WSL.... Registering the task with your password
doesn't start it either. To bring the cluster back now, in an administrator
PowerShell:
Start-ScheduledTask -TaskName 'Mesopod WSL'
wsl --list --running
Once mesopod is listed, the console shows the cluster connected again as
soon as k3s and the outpost are back up.
Running the installer again¶
Running the installer again is the repair for everything above. It merges
its settings back into .wslconfig and /etc/wsl.conf, corrects and enables
the firewall rule, runs the Linux install again (which puts the port hold
back and resumes from the cluster's saved token), and registers the startup
task if it is missing. It leaves a registered task alone.
Which line to run depends on whether your install command's claim has been
spent. It is spent once a run has printed
+ token saved to /etc/mesopod/outpost-token.
- Not spent yet: paste the same line from the console again.
-
Spent: run the line without the claim, in a new PowerShell window:
irm https://console.mesopod.cloud/api/v1/install.ps1 | iexIt finds the
mesopoddistro, asks for permission as before, and resumes. A claim works once, so the full line would now stop with… generate a new install command in the console (HTTP 410). The new window matters for the same reason: the window you pasted the full line into still hasMESOPOD_CLAIMset, and the installer would pass the spent claim on. (Remove-Item Env:MESOPOD_CLAIMclears it.)
The installer also keeps a copy of itself at
%LOCALAPPDATA%\Mesopod\install.ps1, which takes switches. Run it the same
way, in a new window:
& "$env:LOCALAPPDATA\Mesopod\install.ps1" -StartupTaskPassword
& "$env:LOCALAPPDATA\Mesopod\install.ps1" -Memory 8GB
& "$env:LOCALAPPDATA\Mesopod\install.ps1" -Uninstall
-StartupTaskPassword registers the startup task with your Windows password
(above), -Memory caps WSL's memory
(below), and -Uninstall removes Mesopod
(Removing Mesopod). The first two are full repair runs
as well.
When a run changes .wslconfig or /etc/wsl.conf, it restarts WSL and
prints restarting WSL...; afterwards,
start the cluster again.
Memory¶
All WSL distros share one virtual machine, and by default WSL lets it use up to half of the PC's memory. The cluster and every app you deploy live inside that allowance, alongside anything else you run in WSL. To set a cap, in a new PowerShell window:
& "$env:LOCALAPPDATA\Mesopod\install.ps1" -Memory 8GB
This writes memory=8GB under [wsl2] in .wslconfig and restarts WSL so
it takes effect; afterwards,
start the cluster again. Editing
the file yourself works too, followed by wsl --shutdown and the same
restart. Sizes take GB or MB.
4 GB is a comfortable floor for a handful of apps, as on a Linux server.
Storage¶
The distro's own disk¶
Volumes Mesopod provisions for your apps live inside the distro, under
/var/lib/rancher/k3s/storage/, on WSL's own virtual disk. It is an ext4
file system: fast, and it keeps Linux file ownership the way apps expect.
That makes it the place for anything an app writes heavily or checks the
ownership of — databases, photo libraries, app state.
The disk is a single file, ext4.vhdx, kept by default under your Windows
user profile (in %LOCALAPPDATA%). It grows as apps write, up to WSL's
default limit of 1 TB, so the free space on that drive is the free space
your apps have. This prints where it is:
(Get-ChildItem -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss | Where-Object { $_.GetValue("DistributionName") -eq 'mesopod' }).GetValue("BasePath") + "\ext4.vhdx"
Microsoft advises against modifying, moving or even opening that file with
Windows tools. Explorer reaches the distro's files safely at
\\wsl$\mesopod\.
Removing Mesopod deletes all of it: removing the cluster
in the console deletes its volumes, and -Uninstall deletes the disk. Back
up before either. snapshots.md works as written inside the
distro (wsl -d mesopod opens a root shell), and Explorer can copy the
archive out of \\wsl$\mesopod\.
Windows drives¶
WSL mounts each fixed Windows drive in the distro under /mnt/, so
D:\Media is /mnt/d/Media. Apps can use a directory there through a
Node Directory, with two limits:
- Slower. Microsoft recommends keeping the files a Linux program works on in the Linux file system, for speed.
- No Linux ownership. Unless the distro's
/etc/wsl.confturns on WSL'smetadatamount option — it is off by default, and the installer leaves it alone — every file on a Windows drive shows the same owner, its permission bits come from your Windows account's rights to it, and Linux can't change who owns it. Apps that set ownership or permissions on their data, as databases do, can fail there.
So a Windows drive suits files apps mostly read, such as a media library for Plex or Jellyfin. It is not the place for an app's own data — a database, or a library an app writes to and indexes, such as Immich's photos. Keep those on the distro's own disk.
Using a directory from the PC¶
Mesopod mounts a directory that already exists on the server into apps
through a Node Directory (what the other guides call a host directory
volume). Here the server is the distro, so the path is a Linux path inside
it — /mnt/d/Media for D:\Media — never a Windows one.
- In the console, open Volumes → Create volume, choose Node Directory and this PC's cluster, type the Linux path under Directory on your server, and press Create volume.
-
The directory's page shows a command under Confirmed on cluster. Open a shell in the distro with
wsl -d mesopodand run the command there, withsudobeforebashif the console's line doesn't have it:curl -fsSL https://console.mesopod.cloud/api/v1/register-roots.sh | sudo bash -s -- --cluster-id … --cluster … --root '/mnt/d/Media'The script applies the registration with
kubectl, and on a cluster the Linux install built,kubectlreads k3s's own credentials, which only root can read. In themesopoddistro you are root already, sosudochanges nothing there; run as any other user, the command without it ends inno kubectl context reaches mesopod cluster. 3. The console confirms the directory within a minute, or press I've run it — verify now. If verification fails, the remedy line under the chip says why; see troubleshooting.md.
A second drive¶
A second internal drive can serve the cluster in two ways.
As a Windows drive, unchanged. It appears under /mnt/ like any other,
with the limits above, which suits a media library.
Reformatted as ext4 and attached to WSL with wsl --mount, it behaves
like the distro's own disk — fast, with Linux ownership — at a real cost:
- Reformatting erases the drive, and Windows can't read an ext4 drive afterwards.
wsl --mountneeds administrator rights, attaches the whole disk, and refuses a disk Windows is using or the one Windows runs from. USB drives aren't supported.- The attachment doesn't survive WSL stopping:
wsl --shutdownand every restart detach the disk, so it has to be attached again at every boot, before the cluster needs it.
The installer doesn't set this up, and its startup task can't do it: the task
runs without administrator rights, and the installer re-registers it
(-StartupTaskPassword) or removes it (-Uninstall), so don't add to it.
Attaching at boot takes a startup task of your own that runs with highest
privileges. Mesopod hasn't tested this setup.
Once the drive is formatted, attaching it looks like this, in an
administrator PowerShell, with the DeviceID that
Get-CimInstance -Query "SELECT * from Win32_DiskDrive" lists for the drive:
wsl --mount \\.\PHYSICALDRIVE2 --partition 1 --name media
The disk then appears in every distro under /mnt/wsl/media; register
directories there as above. Microsoft's guide,
Mount a Linux disk in WSL 2,
has the details. To format the drive, attach it with --bare instead and
format it from inside WSL; lsblk there lists WSL's own virtual disks next
to it, so check the size before you format anything.
VPN clients¶
Under mirrored networking the distro shares the PC's network connections, so a VPN client running on Windows shapes the cluster's traffic as well as Windows'. Mesopod needs two things from the network, and a VPN can take either away:
- The outpost's connection out, to
bridge.mesopod.cloud:443. A kill switch, which blocks traffic that doesn't go through the tunnel, can cut it; the console then shows the clusterlast bridge: … agoinstead ofconnected. - Connections in from your network, on ports 80 and 443. Some VPN clients block the local network unless you allow it in their settings.
The simplest fix is not running the VPN on this PC. Otherwise allow local
network access in the client, and turn off its kill switch or keep the
cluster's traffic out of the tunnel with its split-tunnelling settings, if
it has them. Mesopod hasn't been tested with any particular VPN client: with
the VPN connected, check that the console shows the cluster connected and
that an app opens from your phone on your home network.
A VPN that carries all traffic can also move the cluster's DNS target. The
outpost reports the address of the network interface that holds the default
route, and with such a VPN up, that can be the VPN's interface rather than
your Wi-Fi or Ethernet adapter. Check the cluster's row in the console: after
dns → it should show the PC's address on your network, which ipconfig
lists under that adapter. If it shows another address, set the right one with
Edit. An address set this way doesn't follow the PC if its address
changes, so give the PC a fixed address on your router (a DHCP reservation).
Reaching your apps from outside the house¶
remote-access-tailscale.md explains the approach: a Tailscale subnet router on your home network, so a device away from home can reach the private address your app names point at. Here that address is the PC's own — mirrored networking is what makes the apps answer there. Under WSL's default networking they would answer only inside the PC, and no route could reach them.
Run the subnet router on another always-on Linux machine on the same network,
such as a Raspberry Pi or a spare mini PC. The guide's steps apply to it
unchanged, IP forwarding included, and traffic arriving through the route
reaches this PC the same way a phone on your Wi-Fi does. The subnet to
advertise is the PC's: ipconfig shows its address and subnet mask
(255.255.255.0 is a /24). Test from a phone on mobile data with Tailscale
connected.
Running the subnet router on this PC itself, with Tailscale's Windows client, isn't covered yet. Tailscale's subnet router documentation says Windows needs IP forwarding turned on before it advertises routes, but doesn't give the steps, and Mesopod hasn't tested that setup.
Troubleshooting¶
The installer names its own fix at every stop. The Windows cases that aren't stops are listed in troubleshooting.md; the problems below are the ones that turn up later.
The cluster is offline after a restart¶
The console's row reads last bridge: … ago and the PC is on. In PowerShell:
wsl --list --running
mesopodisn't listed: the startup task didn't start it. Start the task to bring the cluster back. If it is down after every restart, register the task with your password, as the startup task describes.mesopodis listed: WSL is up, and the problem is inside it.wsl -d mesopod systemctl is-active k3sshould printactive. After that, the checks in troubleshooting.md apply, run inside the distro (wsl -d mesopod), where<kube>issudo k3s kubectl. A VPN client can cut the connection too.
An app opens on this PC but not from your phone¶
In order:
- The firewall rule.
Get-NetFirewallHyperVRule -Name Mesopod, in an administrator PowerShell, showsEnabledTrue(the rule). - The DNS target. The cluster's row shows the PC's address on your
network after
dns →(VPN clients covers a wrong one). - The phone's network. It is on the same network as the PC, and the name resolves there (below).
- A VPN client on the PC that blocks the local network (VPN clients).
An app opens from your phone but not on this PC¶
Host loopback is off. Check that .wslconfig still has
hostAddressLoopback=true under [experimental]; if you add it back, run
wsl --shutdown and start the cluster again.
See host loopback.
An app's name doesn't resolve¶
Ask your own resolver and a public one, in PowerShell:
Resolve-DnsName photos.basement-nuc.mesopod.app
Resolve-DnsName photos.basement-nuc.mesopod.app -Server 8.8.8.8
If the public resolver answers with the PC's address and yours doesn't, your router's DNS rebind protection is dropping the private answer; the fix, by router, is in install.md.
An app is slow, or won't start, on a Windows drive¶
If the app keeps its library or its database in a directory under /mnt/,
that is a Windows drive, with the limits described in
Windows drives. Move the data onto a volume Mesopod
provisions, which lives on the distro's own disk: snapshots.md
covers copying data out and restoring it into another app instance.
Re-running the install line fails with HTTP 410¶
The line still carries a claim that has already been used. Run the line
without it, in a new PowerShell window, as
Running the installer again describes. If
that run prints restarting WSL...,
start the cluster again when it
finishes.
Removing Mesopod¶
Back up first, while your apps still exist: snapshots.md works inside the distro. Removing the cluster in the console removes every app on it and deletes every volume Mesopod provisioned there.
-
Remove the cluster in the console, and wait until it has left the Clusters list. Until then its row shows the removal's progress —
removing apps, …, thendeleting volumes, …, thendecommissioning outpost— and that work runs on this PC, so the outpost must stay up until it is done. If the cluster goes offline, start it again and the removal carries on.If the removal stops for good instead, don't wait for it. The row then says
decommission failed,outpost went quiet after acknowledging decommission, or that the outpost never connected. Use Force remove on the row: it appears once the outpost is disconnected or the removal is ten minutes old, and Mesopod then forgets the cluster. Whatever it leaves on the PC goes in the next step. 2. Then, in a new PowerShell window:& "$env:LOCALAPPDATA\Mesopod\install.ps1" -UninstallIt removes the startup task, the firewall rule, the
mesopoddistro and the installer's own files under%LOCALAPPDATA%\Mesopod. Removing the distro deletes its disk and anything still on it. Directories on Windows drives, and a second drive you attached, are not touched..wslconfigis left as it is, since its settings apply to every distro and you may want to keep them. A distro namedmesopodthat the installer didn't create is never removed: the uninstall says so and leaves it in place.
If you ran -Uninstall before the removal finished, the cluster stays at
removing in the console, because nothing is left on the PC to finish it. Use
Force remove the same way.
Next¶
- first-deploy.md — put an app on it.
- snapshots.md — back your data up before you depend on it.
- troubleshooting.md — keyed by the message you are looking at.