I dropped my MacBook Air M3. Twice. Maybe three times. The screen is black now, and Apple says that’s not covered under warranty because apparently gravity isn’t a manufacturing defect.

So I did what any reasonable person would do: I turned my iPad into a full development workstation. Not “I can edit a file in a pinch” development. Full stack, multiple terminals, VS Code, Claude Code, the works. From a tablet.

Here’s the whole setup, from zero to “wait, that’s not a laptop?”


The Problem With Laptops

A $5,500 MacBook Pro is an incredible machine. It’s also an incredibly expensive thing to carry around. I know this because I killed a $1,300 Air just by being a person who moves through space. The iPad M4 with a Magic Keyboard costs less, survives drops better (the keyboard case is basically armor), and if the keyboard breaks, that’s $300 instead of a trip to the Apple Store where they tell you your warranty doesn’t cover physics.

The missing piece was never hardware. It was: how do I actually write code on this thing?


Step 1: Tailscale (Connect Your Machines)

The insight that makes everything work: your iPad doesn’t need to BE a development machine. It needs to REACH one.

Tailscale creates a mesh network between your devices. Install it on your PC and your iPad, log in on both, and they can talk to each other directly. No port forwarding, no dynamic DNS, no praying your ISP doesn’t change your IP.

The interesting part: Tailscale is a virtual mesh network built on WireGuard. When you enable it, both devices get addresses in the 100.x.x.x range. Traffic goes directly between them, peer-to-peer, encrypted. Tailscale’s servers just handle the introduction: “hey, here’s your PC’s public key and where to find it.” After that, they’re out of the picture.

If both devices are behind NAT (and they are), Tailscale uses UDP hole punching to establish the direct connection. Both sides send packets at each other simultaneously, and the NATs go “oh, we must be expecting a reply” and let the traffic through. It’s social engineering for routers.

The free tier gives you 100 devices and 3 users. Since Tailscale doesn’t actually route your traffic, they’re not eating bandwidth costs. You’re providing the bandwidth. That’s why a “VPN” can be this cheap: it’s not really a VPN.

After setup, you’ll have something like:

my-pc (Windows PC)  - 100.x.x.x
my-ipad (iPad)      - 100.x.x.x

Tailscale also gives you MagicDNS, which means you can use machine names instead of IPs. From Blink on the iPad, ssh my-pc just works. No memorizing addresses. Safari on iPad is a different story: it sometimes treats short hostnames as search queries instead of URLs, so for browser-based tools you might still need the IP or a custom domain.

Two machines that can reach each other from anywhere on the internet. Now we need to do something with that.


Step 2: SSH (Get a Shell)

Tailscale has a built-in SSH server, but it doesn’t support Windows. Classic. So we use Windows’ native OpenSSH server instead.

In an Admin PowerShell:

# Install and start OpenSSH Server
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Start-Service sshd
Set-Service -Name sshd -StartupType 'Automatic'

Now here’s the important part: lock it down. By default, OpenSSH listens on all interfaces. That means anyone on your local network can see it. If you’re on coffee shop WiFi and someone on that network is scanning ports, they’ll find your SSH server wide open.

Tailscale addresses live in the 100.64.0.0/10 CGNAT block. We can use that to only allow SSH connections from Tailscale:

# Only allow SSH from Tailscale
New-NetFirewallRule -DisplayName "SSH - Tailscale Only" `
  -Direction Inbound -LocalPort 22 -Protocol TCP `
  -RemoteAddress 100.64.0.0/10 -Action Allow

# Disable the default wide-open rule
Set-NetFirewallRule -DisplayName "OpenSSH SSH Server (sshd)" -Enabled False

Now SSH is only reachable through your private mesh. A compromised device on your local network scanning port 22 sees nothing.

Landing in WSL

If you’re running WSL (and you probably are if you’re doing real development on Windows), you don’t want to land in PowerShell every time you connect. Set WSL as your default SSH shell:

New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" `
  -Name DefaultShell -Value "C:\Windows\System32\wsl.exe" `
  -PropertyType String -Force

Now ssh user@100.x.x.x drops you straight into bash. No intermediate wsl command needed.

Hardening With SSH Keys

Password auth works, but it’s a liability. If your SSH server is ever exposed to the public internet, or a compromised device on your network finds it, automated brute force attacks start within minutes. Botnets scan for open port 22 constantly and throw dictionaries at it. The Tailscale firewall helps, but defense in depth means not relying on a single layer.

SSH keys use asymmetric cryptography: a matched pair where the private key can prove identity and the public key can verify it, but you can’t derive one from the other. The private key never leaves your device. The server only needs the public half to verify you’re you.

Generate a key in Blink’s Enclave (Settings > Keys > Generate). Enclave is Blink’s secure key storage: the private key is generated and stored inside your iPad’s Secure Enclave hardware chip. It never touches the filesystem, never gets exported, and can’t be extracted even if your iPad is compromised. The chip performs the cryptographic operations internally and only returns the result. Even a jailbreak or full disk access won’t get the key out. Blink will show you the public key. Copy it.

Add that public key to your Windows machine. For Windows OpenSSH, authorized keys for admin users go in a special place:

# On Windows, in Admin PowerShell
Add-Content C:\ProgramData\ssh\administrators_authorized_keys "YOUR_PUBLIC_KEY_HERE"
icacls C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r /grant "SYSTEM:F" /grant "BUILTIN\Administrators:F"

Then disable password auth entirely in C:\ProgramData\ssh\sshd_config:

PubkeyAuthentication yes
PasswordAuthentication no

Restart the service:

Restart-Service sshd

Now only your iPad (with the private key in Enclave) can connect. No passwords to forget, no brute force attacks to worry about. Even if someone intercepts the public key, it’s useless without its other half, which is locked in hardware on your iPad.


Step 3: Mosh (Stop Losing Your Session)

SSH has a problem: it’s TCP. Your session is a connection bound to a specific IP and port on both ends. Close your iPad’s lid, switch from WiFi to cellular, walk through a dead zone for too long, and TCP gives up. Your session is gone. Whatever you were in the middle of? Hope you were running tmux.

Mosh (Mobile Shell, not the dance from the early 2000s) fixes this. It authenticates over SSH, then switches to UDP for the actual session. UDP doesn’t care about connections: it just sends packets. Your iPad gets a new IP because you switched networks? Mosh sends the next packet from the new address and the server goes “oh, there you are.”

Install it on your WSL machine:

sudo apt install mosh

Open the UDP ports on Windows (Tailscale-only, same as SSH):

New-NetFirewallRule -DisplayName "Mosh - Tailscale Only" `
  -Direction Inbound -LocalPort 60000-61000 -Protocol UDP `
  -RemoteAddress 100.64.0.0/10 -Action Allow

Connect from Blink:

mosh user@100.x.x.x

Now close your iPad’s lid. Wait. Open it. Your session is still there. Switch from WiFi to your phone’s hotspot. Still there. Walk to a different coffee shop entirely. Still. There.

Mosh also does local echo: it predicts your keystrokes instead of waiting for the server round-trip, so typing feels instant even on a bad connection.


Step 4: VS Code Tunnel (Optional)

If you want a graphical editor alongside your terminal sessions, VS Code tunnels give you the full VS Code experience in a browser.

The code command in WSL opens the Windows GUI, which isn’t helpful here. Download the standalone CLI:

curl -Lk 'https://code.visualstudio.com/sha/download?build=stable&os=cli-alpine-x64' \
  --output vscode_cli.tar.gz
tar -xf vscode_cli.tar.gz
mv code ~/.local/bin/code-tunnel

Start the tunnel:

code-tunnel tunnel

It’ll ask you to authenticate with GitHub. After that, you get a URL like https://vscode.dev/tunnel/your-machine-name that gives you full VS Code in any browser. Make it persistent:

code-tunnel service install

Fair warning: if you’re using WSL, this runs as a systemd service inside WSL. It only starts when WSL is running. SSH in first (which starts WSL), and the tunnel comes up automatically.


The Workflow

Here’s what my actual day looks like from the iPad:

Blink handles terminals. Cmd+T opens new tabs. Each tab SSHs (or Moshes) into my PC and lands in WSL. One tab for Claude Code, one for git, one for the dev server, one for whatever else. Custom fonts, proper terminal rendering, Nerd Font icons in my prompt, all working perfectly because it’s a real terminal doing terminal things.

Safari runs vscode.dev for when I want a file tree and multi-file editing. It’s a real browser, not a webview wrapper, so it actually works properly.

Chrome is the test browser for whatever I’m building.

iPad Split View or Stage Manager tiles them all. Full-screen Blink with a retina M4 display is, frankly, gorgeous. People see the Magic Keyboard and assume it’s a laptop. They freak out when I detach the screen.

The compute happens on my PC at home: a desktop that’s always on, always plugged in, connected to power and ethernet. It doesn’t need to survive being carried through an airport. It just needs to be reachable. Tailscale handles that part.


Why This Beats a Laptop

The iPad M4 with Magic Keyboard costs less than half of a specced-out MacBook Pro. It’s lighter, the screen is better for its size, and the case turns it into something that actually survives daily life.

But here’s the real thing: I have a MacBook Pro on order. 128GB unified memory, M4 Max. It’s going to be an incredible machine for running local LLMs and heavy compute. And it is never leaving the house. It goes on the desk, plugged into power, and joins the fleet of headless machines I SSH into from my iPad.

Some people buy expensive laptops and carry them everywhere. I buy expensive computers and leave them plugged in where they’re safe. Then I carry the cheap, durable thing that connects to all of them.

The iPad is the interface. The compute lives somewhere else. And Tailscale is the glue that makes “somewhere else” feel like it’s right in front of you.


Gotchas

A few things that will bite you if nobody warns you:

WSL2 Is a VM, Not Your Machine

This is the one that gets everyone. WSL2 isn’t a compatibility layer sitting on top of Windows. It’s a full Linux kernel running inside a lightweight Hyper-V virtual machine. Windows and WSL2 are sibling VMs managed by a hypervisor underneath both of them.

This is different from WSL1, which was a syscall translation layer running directly inside Windows. WSL1 shared Windows’ network stack, so localhost was the same localhost. WSL2 traded that convenience for a real Linux kernel with full syscall compatibility, but the cost is network isolation.

Why this matters: WSL2 has its own network stack with its own IP address. When you run a dev server on localhost:3000 inside WSL2, that’s the VM’s localhost, not Windows’ localhost. From your iPad, you’re connecting to Windows via Tailscale, so 100.x.x.x:3000 hits Windows’ network stack, which doesn’t see ports opened inside the WSL2 VM. They’re two separate machines sharing a screen.

The fix is port proxying. Windows can forward traffic from its network interfaces into WSL:

# Forward port 3000 from Windows to WSL
netsh interface portproxy add v4tov4 listenport=3000 listenaddress=0.0.0.0 connectport=3000 connectaddress=$(wsl hostname -I | cut -d' ' -f1)

You’ll also need a firewall rule to allow inbound traffic on that port (locked to Tailscale, of course):

New-NetFirewallRule -DisplayName "Dev Server - Tailscale Only" `
  -Direction Inbound -LocalPort 3000 -Protocol TCP `
  -RemoteAddress 100.64.0.0/10 -Action Allow

The catch: WSL’s IP can change between reboots. You’ll want to re-run the portproxy command after a restart, or script it into your WSL startup. Welcome to the Windows-Linux marriage.

Safari Treats Short Hostnames as Search Queries

Type my-pc:3000 into Safari on iPad and it googles it. You need the full http://my-pc:3000 with the protocol prefix. Blink handles short hostnames fine because it’s a terminal, not a browser pretending to be smart.

.dev Domains Force HTTPS

If you set up a custom domain like mybox.something.dev for your Tailscale machine, every browser will force HTTPS. Google owns the .dev TLD and put the entire thing on the HSTS preload list. There’s no way to disable it. You’ll need a real SSL cert or pick a different TLD.


The Full Stack

For anyone who wants to replicate this:

  1. Tailscale on both devices (free tier is fine)
  2. Windows OpenSSH Server, firewall locked to 100.64.0.0/10
  3. SSH keys for passwordless auth
  4. WSL as default shell so you land in Linux
  5. Mosh for connection persistence
  6. VS Code tunnel (optional) for graphical editing
  7. Blink on iPad for terminal access

Total cost beyond the hardware you already own: $0.

Your iPad is not a toy. It just needed a reason to prove it.