はじめに
チームでは、ある Rails 製の業務アプリを開発しています。
- Ruby 3 / Rails 7 / MySQL 8 / Puma / Sidekiq + Redis
- Hotwire(Turbo + Stimulus)、Import maps、Sass
- ストレージ:S3 互換のオブジェクトストレージ(開発環境では MinIO)
このプロジェクトは当初 M1 Mac で開発されており、Docker 構成も手順書も Mac 前提でした。それを Windows に移し、VS Code で開発できる環境を整えることになりました。
Mac では「Docker Desktop を入れて docker-compose up」(現在の Compose V2 では docker compose up)で済んでいた作業が、Windows では思った以上に大変でした。
この記事では、VS Code を中心とした開発環境を作るなかで苦労したことと、その解決方法をまとめます。
うまくいかなかったことや遠回りしたことも含めて書きます。
苦労1:Mac 前提の構成がそのまま動かない
もともとの手順書には「M1 Mac 以外なら platform を linux/amd64 に書き換える」という注意書きがありました。
一般に、x86_64(amd64)の Windows マシンで ARM 向け(linux/arm64)のイメージを指定すると、エミュレーションで動いて極端に遅くなったり、exec format error で起動しなかったりします。逆も同様で、platform の指定は CPU ごとに正しく切り替える必要があります。
services:
db:
image: mysql:8.0
platform: linux/amd64 # M1 Mac 以外ではここを書き換える必要があった
VS Code の設定以前に、こうした「特定の OS・CPU を暗黙の前提にした設定」を一つずつ見つけて直す必要がありました。
苦労2:とにかく重い
Windows のフォルダで動かすと遅い
Windows のフォルダ上で Docker を動かすと非常に遅かったため、プロジェクトは WSL(Ubuntu)側に clone し、code . で VS Code を WSL モードで開くようにしました。一般に、Windows 側のファイル(WSL から見た /mnt/c/...)を WSL2 やコンテナから扱うと、OS をまたいだファイルアクセスになるため I/O が遅くなりやすいとされています。WSL 側(~/ 以下)に置けば、OS をまたいだアクセスが発生しません。なお、WSL 側のファイルは Windows のエクスプローラーから \\wsl$\Ubuntu\home\<ユーザー名> で開けます。
# WSL(Ubuntu)のターミナルで
git clone <your-repository-url>
cd myapp
code .
全部入りの Compose が重い
全サービスが起動する構成も重かったため、Selenium 用のブラウザと MinIO まわりは Compose の profiles で必要なときだけ起動する形にし、web の depends_on からも minio / redis を外しました。
ARM 向けイメージのエミュレーションが一因だった可能性もありますが、推測の域を出ません。
profiles を指定したサービスは、通常の docker compose up では起動せず、--profile を付けたときだけ起動します。
services:
minio:
image: minio/minio
profiles: ["storage"]
selenium:
image: selenium/standalone-chromium
profiles: ["test"]
# MinIO も使うときだけ
docker compose --profile storage up
なお、depends_on から外したサービスは web と一緒には自動起動しなくなります。Sidekiq でジョブを動かすなど Redis が必要な作業では、docker compose up -d redis のように個別に起動します。
苦労3:改行コード
Windows で編集したファイルが入り込み、改行コード(CRLF / LF)が混在してしまいました。CRLF のシェルスクリプト(entrypoint.sh など)はコンテナ内で正しく実行できないため、リポジトリ全体を LF に揃え直しました。
ファイルごとの改行コードは、VS Code のステータスバーで確認できます。
揃え直すときは、リポジトリ直下の .gitattributes で改行コードを LF に固定するのが一般的です。
* text=auto eol=lf
既存のファイルにも反映するには、.gitattributes を追加したあとに次のコマンドで正規化してコミットします。
git add --renormalize .
git commit -m "Normalize line endings to LF"
あわせて、Windows 側の Git で core.autocrlf が true になっていると、チェックアウト時に CRLF へ変換されることがあります。WSL 側で clone していれば影響は受けにくいですが、設定を確認しておくと安心です。
苦労4:Ruby LSP と Bundler
Ruby LSP は Bundler 経由で動くため、gem が正しく入らないとエディタの補完も動きません。
また、WSL モードや Dev Containers で開いている場合、Ruby LSP の拡張機能は Windows 側ではなく WSL やコンテナ側にインストールされ、その環境の Ruby と Bundler で動きます。補完が効かないときは、まず VS Code の「出力」パネルで Ruby LSP のログを確認すると、どの Ruby で起動しようとしているか、どの gem で失敗しているかがわかります。
.env を読み込むために dotenv-rails を development/test グループに追加したところ、Docker のビルドが失敗したため、いったん元に戻しました。
原因は、Docker のビルドで development/test グループの gem がインストールされない状態になっていたことだと考えられます。そこで、Dockerfile のマルチステージビルドの両ステージで Bundler のグループを明示し、あわせて gem の保存先をボリュームに変更しました。
苦労5:初回セットアップの手順が長い
Mac では 4 ステップ程度だった手順が、Windows では WSL2 の導入、設定ファイルのリネーム、依存ライブラリの準備、アセットのプリコンパイル、DB の初期化などへと大きく膨らみました。
DB の作成・マイグレーション・seed は entrypoint.sh にまとめ、スキップ用の環境変数(以下「SKIP フラグ」)が空のときだけ実行するようにしました。初回だけ DB を初期化し、2 回目以降はフラグを設定してスキップする運用です。イメージは次のとおりです(変数名は例です)。
#!/bin/bash
set -e
# 前回の異常終了で残った PID ファイルを削除
rm -f tmp/pids/server.pid
if [ -z "${SKIP_DB_SETUP}" ]; then
bin/rails db:create db:migrate db:seed
fi
exec "$@"
また、WSL に直接 Ruby を入れる手順と Docker を使う手順が併存しており、手順書が複雑になってしまいました。
おまけ:AGENTS.md を置いた
AI コーディングエージェント向けに、プロジェクト概要・技術スタック・構成をまとめた AGENTS.md を置きました。構成だけを短く伝えられる場所があると便利でした。
振り返り:次にやるなら
VS Code の設定をリポジトリに含める
当時、リポジトリには .vscode/ も .devcontainer/ もなく、拡張機能も設定も手順書の文章だけで共有していました。文章は読み飛ばされやすく、更新も漏れがちです。
推奨拡張機能を .vscode/extensions.json に書いておくと、リポジトリを開いたときに VS Code がインストールを提案してくれます。
{
"recommendations": [
"ms-azuretools.vscode-docker",
"ms-vscode-remote.remote-wsl",
"ms-vscode-remote.remote-containers",
"shopify.ruby-lsp"
]
}
エディタ設定は .vscode/settings.json に置けます。たとえば改行コードは、.gitattributes に加えてエディタ側でも LF に揃えておくと安心です。
{
"files.eol": "\n"
}
さらに進めるなら、devcontainer.json でコンテナ定義ごと共有し、Dev Containers で OS の差を吸収する方法もあります。
そのほかの反省
-
OS・CPU を決め打ちしない:マルチアーキテクチャ対応のイメージを選ぶか、
platform: ${DB_PLATFORM:-linux/amd64}のように環境変数で切り替えられるようにしておく -
DB の接続先は一つに決めておく:一般論として、ホスト側の DB(
host.docker.internal経由)とコンテナ内の DB を併用できる状態にすると、どちらに接続しているのか混乱しやすい -
SKIP フラグの手動切り替えをやめ、ワンショットのコマンドにする:
docker compose run --rm web bin/setupのような形にできる - gem をアーカイブなどで配布する方法は応急処置にとどめる:一般に、リポジトリの肥大化や更新漏れのリスクがあるため、Bundler でインストールできる状態を目指す
まとめ
- ソースは WSL 側に置き、VS Code は WSL モードで開く
- 重いサービスは profiles で任意起動にする
- 改行コードは
.gitattributesとエディタ設定で LF に固定する - Ruby LSP は Bundler 頼み。gem が入らないと補完も動かない
- 環境に依存する部分は、環境変数で切り替えられる形に直しながら育てる
- VS Code の設定は文章ではなく、
.vscode/やdevcontainer.jsonで共有する
VS Code の環境構築で苦労した原因の多くは、エディタそのものではなく「特定の OS・CPU を前提にした構成」と「文章だけで共有していた設定」にありました。同じように Windows で Rails 開発を始める方の参考になればうれしいです。