10年Macに引きこもったiOSエンジニアがWindows 11 + WSL2 + Dockerでサーバー環境を作った話
iOSアプリを個人で開発しながら、業務委託でも企業のアプリ開発に関わっている。使う道具はずっとMacだった。Xcodeも、実機テストも、ビルドも全部Mac。Windowsを触る理由が10年ほどなかった。
そこに子供が大学を卒業して、ノートPCが空いた。
Windows 11、Intel Core i7、16GB RAM。サーバー環境を動かすには十分なスペックだった。もともとMacのDocker上でサーバー環境を作るつもりだったが、方針を変えることにした。
なぜリモートデスクトップにしたか
Windows 11のノートPCを受け取って最初に考えたのは、このPCをどうやって使うかだった。
ずっとUSキーボードを使ってきた。理由はシンプルで、カナ打ちができないからシンプルなキーボードがいい、それだけだ。子供から受け継いだWindowsはノートPCなのでキーボードはJIS配列。Bluetoothキーボードも持っているが、狭い机にPCをもう一台置くのは避けたかった。
MacとWindowsを交互に使うとなると、キーボード配列の違いだけで相当ストレスになる。JISとUSを行き来するたびに打ち間違える未来が見えた。
MicrosoftがMac向けに「Windows App」(旧Microsoft Remote Desktop)を出しているのは知っていたので、試してみることにした。
結果としてこれが正解だった。MacのキーボードとトラックパッドだけでWindowsを操作できる。Windows用のマウスも不要になり、机の上がスッキリした。

Microsoftアカウントの罠
Windows Appでの接続設定は簡単だった。WindowsのIPアドレスを入れて、ユーザー名とパスワードを入力するだけのはずだった。
ところが繋がらない。
Windowsのアカウントはセットアップ時にMicrosoftアカウントで作っていた。パスワードを変更してもダメ。サインインを試みると、PINか顔認証の2択しか出てこない。
ここで初めて知ったのだが、リモートデスクトップ(RDP)プロトコルはWindows Hello(PIN・顔認証)に対応していない。MicrosoftアカウントでもRDP接続できるケースはあるようだが、少なくとも自分の環境ではWindows Hello(PIN・顔認証)中心のサインインとRDPのパスワード認証が噛み合わず、うまく接続できなかった。
解決策はローカルアカウントを別途作成することだった。
設定 → アカウント → 他のユーザー → アカウントの追加
ここで「Microsoftアカウントなし」を選んでローカルアカウントを作る。管理者権限を付与して、このアカウントのユーザー名とパスワードでRDP接続すると、あっさり繋がった。
Microsoftとしては全員Microsoftアカウントで使ってほしいのだろう。ローカルアカウントの作り方は意図的に分かりにくい場所に置かれている。
IPアドレスではなくPC名で接続する
最初はWindows側のIPアドレス(192.168.0.47など)でWindows Appの接続先を設定していた。これで動いていたが、Windowsを再起動したり、途中でWi-Fiルーターを変えたりすると、DHCPによってIPアドレスが変わってしまう。そのたびにWindows Appの設定を修正する必要があった。
試しに、Windowsの 設定 → システム → リモートデスクトップ に表示されているPC名(例:DESKTOP-XXXXXXX)をWindows Appの接続先に設定してみたところ、自分の環境では接続できた。
ただし、これは環境依存の挙動のようだ。実際にMacのターミナルから ping DESKTOP-XXXXXXX を実行しても名前解決はできなかった。Windows App側がどのようにPC名を解決しているのかは正確には分かっていない。
安定させるなら、ルーター側でWindows PCに固定IPを割り当てる方が確実だと思う。自分の環境ではひとまずPC名でRDP接続できているので、このまま使っている。
WSL2のセットアップ
リモートデスクトップの問題が解決したので、次はWSL2(Windows Subsystem for Linux 2)を入れた。
WSL2はWindows上でLinuxカーネルをネイティブに動かす仕組みで、Hyper-V技術をベースにした仮想化環境だ。普通の仮想マシンより軽量で、WindowsとLinuxのファイルシステムを行き来できる。
PowerShellで以下を実行するだけでUbuntuが入る。
wsl --install -d Ubuntu
インストール後にユーザー名とパスワードを設定して完了。あとは通常のUbuntu環境と同じだ。

WSL2の中に必要なツールをインストールした。
# Node.js / npm
sudo apt update && sudo apt install -y nodejs npm
# Docker
sudo apt install -y docker.io
sudo service docker start
# Claude Code
sudo npm install -g @anthropic-ai/claude-code
データ消失の罠
ここで一つ失敗した。
最初はMicrosoftアカウントに紐づいたWindowsアカウントを作り、その環境でWSL2のセットアップ(Node.js・Docker・Claude Code・GitHubのSSH設定)を進めていた。リモートデスクトップで詰まり、ローカルアカウントを作って解決した後、最初のMicrosoftアカウントを削除した。
すると、WSL2の環境が丸ごと消えた。
WSL2のデータはWindowsのユーザープロファイルに紐づいている。アカウントを削除すると、その中のLinux環境ごと消える。作業中のデータがあれば大変なことになっていた。
WSL2の環境を作り込んでから別のWindowsアカウントに移行したい場合、事前にWSL2のエクスポート(wsl --export)が必要だ。
WebApiLabを動かす
WSL2の環境を作り直してGitHubからプロジェクトをcloneした。プロジェクト名はWebApiLab。Apache httpd + Node.jsをDocker Composeで動かす構成だ。
cd WebApiLab
sudo docker-compose up
これだけでApache(リバースプロキシ)とNode.jsアプリが起動する。WSL2の内部では動いているが、Windowsのブラウザからアクセスするにはひと手間必要だった。
docker ps で確認するとポートが 0.0.0.0:8080->80/tcp になっている。WSL2の内部IPアドレス(ip addr show eth0で確認)でブラウザからアクセスすると繋がった。
MacとiPhoneからアクセスするための設定
WSL2上のDockerコンテナはWSL2の内部IPアドレス空間(172.x.x.x)に存在する。MacやiPhoneは家のLANのアドレス空間(192.168.x.x)にいるので、そのままではアクセスできない。
PowerShellで2つの設定を行う。
# ポートフォワーディング(管理者権限で実行)
netsh interface portproxy add v4tov4 listenport=8080 listenaddress=0.0.0.0 connectport=8080 connectaddress=172.29.237.61
# ファイアウォール許可(管理者権限で実行)
netsh advfirewall firewall add rule name="WSL2 8080" dir=in action=allow protocol=TCP localport=8080
これでWindowsのIPアドレス(192.168.0.47)の8080番ポートに来たアクセスが、WSL2内部のDockerコンテナに転送される。
MacのSafariで http://192.168.0.47:8080/app/ にアクセスすると Hello from Node.js app! が返ってきた。iPhoneのSafariでも同じURLで確認できた。

当初の計画からの変更
最初はMacのDocker上でサーバー環境を完結させるつもりだった。
子供のPCが空いたことで方針を変えた。サーバー環境を別マシンに分離した方が、開発と実行の役割が明確になる。MacのHDの空き容量を消費しなくて済む(Dockerイメージは積み重なると大きくなる)。何より「開発機(Mac)→ GitHub → サーバー機(Windows/WSL2)」というワークフローが、実際の現場に近い構成になる。
Windowsを本番サーバーとして外部に公開するつもりはないが、ローカルで開発してGitHub経由でサーバーに反映するという流れの感覚を掴む環境としては十分だ。
10年ぶりのWindowsで詰まったこと
10年ぶりにWindowsを触って詰まった点をまとめておく。
USキーボードが認識されない
接続直後はJIS配列として認識された。設定 → 時刻と言語 → 言語と地域 → 日本語のオプション でキーボードを「英語(101/102キー)」に変更して再起動が必要。
Windows Updateが止まらない
セットアップ当初、6個のアップデートが順番に走り、1つ終わるたびに再起動を求められた。プログレスバーもなく画面が真っ暗になるので不安だが、待つしかない。
再起動が多い
キーボード配列の変更にも再起動が必要だった。Macに慣れていると、設定変更のたびに再起動を求められることへの耐性がなくなっている。
スリープとカバーの設定
リモートデスクトップで使うには、Windowsがスリープしないようにしておく必要がある。設定 → システム → 電源 → スリープをオフ と カバーを閉じたときの動作を「何もしない」 に設定した。これでカバーを閉じたまま棚に置いておける。
サーバーサイドの経験を積みたいと思っていたところに、ちょうど環境が手に入った。まだWebApiLabは Hello from Node.js app! を返すだけの状態だが、ここにAPIを追加してAWSと連携させていく予定だ。
Windowsに詳しい人から見ると遠回りな部分もあると思うが、Mac中心で開発してきた自分にとっては、OS・WSL2・Docker・LANの境界を理解する良い題材になった。
Discussion