Skip to content

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:443 and bridge.mesopod.cloud:443, and during the install also WSL's own downloads and get.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 | iex

    It finds the mesopod distro, 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 has MESOPOD_CLAIM set, and the installer would pass the spent claim on. (Remove-Item Env:MESOPOD_CLAIM clears 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.conf turns on WSL's metadata mount 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.

  1. 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.
  2. The directory's page shows a command under Confirmed on cluster. Open a shell in the distro with wsl -d mesopod and run the command there, with sudo before bash if 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, kubectl reads k3s's own credentials, which only root can read. In the mesopod distro you are root already, so sudo changes nothing there; run as any other user, the command without it ends in no 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 --mount needs 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 --shutdown and 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 cluster last bridge: … ago instead of connected.
  • 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
  • mesopod isn'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.
  • mesopod is listed: WSL is up, and the problem is inside it. wsl -d mesopod systemctl is-active k3s should print active. After that, the checks in troubleshooting.md apply, run inside the distro (wsl -d mesopod), where <kube> is sudo k3s kubectl. A VPN client can cut the connection too.

An app opens on this PC but not from your phone

In order:

  1. The firewall rule. Get-NetFirewallHyperVRule -Name Mesopod, in an administrator PowerShell, shows Enabled True (the rule).
  2. The DNS target. The cluster's row shows the PC's address on your network after dns → (VPN clients covers a wrong one).
  3. The phone's network. It is on the same network as the PC, and the name resolves there (below).
  4. 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.

  1. 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, …, then deleting volumes, …, then decommissioning 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" -Uninstall

    It removes the startup task, the firewall rule, the mesopod distro 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. .wslconfig is left as it is, since its settings apply to every distro and you may want to keep them. A distro named mesopod that 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