既存のDocker環境(rails7-on-docker)をDevContainer化した話

# 結論
rails7-on-docker環境の`compose.yaml`を使用してDevContainer環境を構築するのがおすすめ！

# 環境
* VScode
* [rails7-on-docker](https://github.com/ryanwi/rails7-on-docker)環境の準備が整っていること
    * 他環境のDevContainer化でも参考になると思いますが、記事自体はrails7-on-railsについて言及しています。
    * `rails7-on-docker`は、docker環境にrails7、postgresql、RSpecなどが入っています。いちいちrubyをインストールしたりだとかをしなくていいので便利です。
    * もし環境準備がまだであればこちらの記事がおすすめです：[Windows WSL Docker Rails7 環境構築](https://qiita.com/mt-blue-sou/items/47e76c37eebf5ad7e603)
    

# dovcontainer化する手順
VScode公式のDEV CONTAINERSの[Quick start open an existing folder in a container](https://code.visualstudio.com/docs/devcontainers/containers#_quick-start-open-an-existing-folder-in-a-container)のところを参考に作業を進めます。
以下は、その手順を日本語で書いたものです。

1. VScode拡張機能`Dev Contaiers`をインストール
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3829496/51b1a40e-a252-fae4-74f3-b4515b09f153.png)
    * `Dev Containers`も含まれた拡張パックの`Remote Development`のインストールでも大丈夫です。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3829496/12fdd9a4-b553-0707-ddde-b221c276d6b7.png)


1. VScode上で、`F1キー`を押してコマンドパレットを開き、「Dev Containers: Open Folder in Container...(開発コンテナ：コンテナーでフォルダを開く…)」をクリック

1. rails7-on-dockerのローカルリポジトリフォルダを選択

1. 「ワークスペースに構成を追加する」を選択
    * コードはgit上で管理するので上記の選択しています。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3829496/66f47465-368f-8a1e-6479-87f9324759ac.png)


1. 「既存の構成をワークスペースにコピーする」を選択
    * 画像は「定義済み…」を選択しているように見えますが、「既存の…」の方を選んでください。（画像撮り直せたら差し替えておきます。）
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3829496/b9727ca6-9ba5-cbe7-e526-7ea86214fa9e.png)

1. `compose.yaml`を選択

1. インストールしたい拡張機能を選択
    * 特になければスキップで大丈夫です。あとで追加できますし、追加したらリビルドすればいいので。

1. VScodeが自動でDevContainer構成ファイルを作成し、開発コンテナ環境をVScodeが開いてくれるのを待つ
    * 新たに「.devcontainer/devcontainer.json」ファイルが作成されます。

1. VScodeの左下に「開発コンテナ：…」になっていれば完成
    * 画像は「開発コンテナ：rails…」になっていますが、デフォルトは違いました。`devcontainer.json`ファイルの`"name"`を`"rails"`にしたのでそうなってます。これもデフォ状態の画像が撮れたら、差し替えておきます。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3829496/f1206770-eaf6-dfd2-ef0c-f92106bc9157.png)

1. 開発コンテナーを終了するときは、VScode左下の「開発コンテナー：…」をクリックし、「フォルダーをローカルで再度開く」を選択
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3829496/36419deb-92e2-254f-6624-2d24f4aed5bc.png)

1. DevContainerを開始するときは、VScode左下の「リモートウィンドウを開きます」をクリックし、「コンテナーでフォルダを開く…」を選択
    * VScodeが`devcontainer.json`を読み込んで、起動してくれます。

### DevContiner構成ファイル
* 公式リファレンスページ：[General devcontainer.json properties](https://containers.dev/implementors/json_reference/)

# 余談
ここからは余談です。本編には関係ないですが、エンジニアには大切だと思うので、ここに記しておきます。

## 事象
[rails7-on-docker](https://github.com/ryanwi/rails7-on-docker)をクローンした環境を使用して開発していた。途中からDevContiner環境にして作業していた。
RSpecを実行してみたら、テスト結果がエラー「missing `secret_key_base` for 'production' environment, set this string with `bin/rails credentials:edit`」になってしまった。DevContiner化する前は、エラーは出ていなかった。いろいろ調べて試していたら、rspecだけでなく`git commit`も`rails c`も、実行するとエラーが返ってきて、「何もできん」になっていることに気づいた。

## 原因
DevContiner環境構築時にproductionモードで実行する`Dockerfile`を選択していたため、productionモードで起動するようになっていた。

## どうしてそうなったか
DevContainer環境構築の「既存の構成をワークスペースにコピーする」ときに、`Dockerfile`を選択してしまい、productionモードで起動するDevContainer環境が出来上がってしまった。

### Dockerfile

```:Dockerfile

    # syntax = docker/dockerfile:1
    
    # This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
    # docker build -t my-app .
    # docker run -d -p 80:80 -p 443:443 --name my-app -e RAILS_MASTER_KEY=<value from config/master.key> my-app
    
    # Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
    ARG RUBY_VERSION=3.3.2
    FROM docker.io/library/ruby:$RUBY_VERSION-slim as base
    
    # Rails app lives here
    WORKDIR /rails
    
    # Install base packages
    RUN apt-get update -qq && \
        apt-get install --no-install-recommends -y curl libjemalloc2 libvips postgresql-client && \
        rm -rf /var/lib/apt/lists /var/cache/apt/archives
    
    # Set production environment
    ENV RAILS_ENV="production" \    # ←ココ
        BUNDLE_DEPLOYMENT="1" \
        BUNDLE_PATH="/usr/local/bundle" \
        BUNDLE_WITHOUT="development"
        
    (省略)
```
    
ちゃんとコメントに「これはプロダクションのためにデザインしたDokerfileだよ、開発用じゃないよ」と書いてありますし、`ENV RAILS_ENV="production"`と設定されていますね。

### compose.yml
```:conpose.yml
services:
  web:
    build:
      context: ./
      dockerfile: development.Dockerfile　# ←ココ
    command: bash -c "rm -f tmp/pids/server.pid && bin/rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/usr/src/app
      - bundle:/usr/local/bundle
    ports:
      - "3000:3000"
    env_file:
      - .env
    environment:
      - HISTFILE=/usr/src/app/log/.bash_history 
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
        
    (省略)
```
ビルドするとき参照する`dockerfile`は、`development.Dockerfile`って書いてあります。開発環境用のDockerfileも用意してありますね（作った人ありがとう）。
DevContiner環境を作成するときに、`development.Dockerfile`を選択しても多分問題なかったと思いますが、`compose.yaml`が`development.Dockerfile`を参照しているし、今後dockerの構成を`compose.yaml`に書くかもしれないので、今回は上記手順で構築しました。

## Dockerfileとcompose.yamlについて
これについてまとめてある記事がありました。
[Dockerfile と docker-compose を利用すると何がうれしいのか？](https://qiita.com/sugurutakahashi12345/items/0b1ceb92c9240aacca02)

簡単に説明すると、`Dockerfile`はdockerイメージ（dockerの中身）の設定を書くところで、`compose.yaml`はdockerを組み合わせてアプリケーションを実行するときの設定を書くところです。

## devcontainer.jsonの差分
```:devcontainer.json（Dockerfileで構成した場合）
{
	"name": "rails",
	"build": {
		// Sets the run context to one level up instead of the .devcontainer folder.
		"context": "..",
		// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
		"dockerfile": "../Dockerfile"
	},

     (略)
}
```
```:devcontainer.json（compose.yamlで構成した場合）
{
	"name": "rails",

	// Update the 'dockerComposeFile' list if you have more compose files or use different names.
	// The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
	"dockerComposeFile": [
		"../compose.yaml",
		"docker-compose.yml"
	],

    (略)
}
```
選んだファイルによって少し書き方が変わっていますね。
`Dockerfile`の方は、`build.dockerlife`プロパティに書かれています。
`compose.yaml`の方は、`dockerComposeFile`プロパティに書かれています。rails7-on-rails環境だと、`dockerComposeFile`プロパティに書いてある`compose.yaml`ファイルを参照して、そこの`build.dockerfile`プロバティを見て、`development.Dockerfile`を読みに行くんだろうなと思います。

## そもそもどうしてDevContainer化しようとしたか
これこそ余談で、話せば長くなるんですが、もとはRailsチュートリアルをrails7-on-docker環境使ってやっていていたんです。んで、「3.6.2Guardによるテストの自動化」のところにきて`Guard`使ってみたんですが、ファイル変更しても反応がないんですね。原因は、`Guard`はコンテナ内で起動していて、ローカルのファイルを操作していたので、いくら保存しても動かないわけだわってなりました。「たしか、`DevConteiner`ってコンテナの中で作業するやつがあった気がする」となり、DevContainer化しようってなりました。
そう、目的は「`Guard`が動く」だったんです。DevContiner環境じゃなくても良かったんですね。他の解決方法もあったかと思いますし、DevContiner環境ができただけで、`Guard`が動くかは試してみないと分かりません。

私は、問題があって、解決方法をいろいろ試しているうちに、また新たな問題に出くわして、その解決方法を調べ始めがちです。これはこれでいろんなことが学べるので悪いことではないかと思います。でも、勤務中のチームでの作業ですと、進捗に影響を与えかねません。他のメンバーからみたら迷子に見えるかもしれません。
その現場の環境や文化にもよると思いますが、報告・相談をしながら進めるといいと思います。知らせておくと、周りも状況を把握しておけますし、アドバイスがもらえるかもしれません。（私は自己学習でやっていたことなので、誰にも迷惑かけてませんよ）

# まとめ・所感
Qiitaに投稿する記事はこれが初めてです。
ずらずらと長く書きましたが、ここに至るまで紆余曲折あったので許してください。今思えば、手順の話しと余談の話しで記事を分けても良かったなとも思っています。今後も時間があれば、投稿したいと思うので、読みやすさとかも考えて書いていきたいです。
最後に、この問題を解決するのに大切だと感じたことは、
* 設定ファイルの役割を把握し、内容を読むこと
* 目的を見失わないこと、たまに振り返ること

です。
DevContainer構築することによって、devcontainer.jsonだけでなく、Dockerfileのことや、Railsサーバー、RSpecなどいろいろなことを調べ、学ぶことができて良かったです。

もし、内容の不備やアドバイスがあれば、コメントいただければと思います。
読んでいただき、ありがとうございました。