devcontainerを使った自分のPython開発環境

# :hatching_chick: はじめに

Qiita初記事として自分のPython開発環境の備忘録を書いていきます！ <sup>[1.](#pray-引用参考文献)</sup>

前まではコードエディターとしてVSCode、仮想環境構築にAnacondaを使っていたんですが、Dockerの勉強を兼ねてdevcontainerを使った開発環境構築を試みました。
その間に調べたことをまとめていきます！

少しでも参考になる箇所があればうれしいです！！

# :thumbsup: 本記事の要点

- [devcontainerの（自分なり）ベストプラクティスの紹介](#whale2-devcontainerの自分なりベストプラクティス)
- [devcontainerを構成するファイルの解説](#notepad_spiral-構成ファイルの詳細解説メモ)

# :no_good: 本記事では扱わない内容

- VSCodeのインストール
- DockerやWSLのインストール
- Remote-containerの追加
- devcontainerの起動
- DockerやDocker Composeの説明や仕組み

# :whale2: devcontainerの（自分なり）ベストプラクティス

## ディレクトリ構造

```text
.
├─ .devcontainer
|  ├─ compose.yaml
|  └─ devcontainer.json
├─ Dockerfile
├─ requirements.txt
└─ ... (使うスクリプトなどを置く)
```

## 構成ファイル

```yaml: compose.yaml
version: '3'

services:
    app:
        command: sleep infinity
        build:
            context: ../
            dockerfile: Dockerfile
        volumes:
            - ../:/workspace
```

```json: devcotainer.json
{
    "dockerComposeFile": ["compose.yaml"],
    "service": "app",
	"workspaceFolder": "/workspace",
	"extensions": [
        "coenraads.bracket-pair-colorizer",
        "dongli.python-preview",
		"esbenp.prettier-vscode",
		"ionutvmi.path-autocomplete",
		"kevinrose.vsc-python-indent",
		"mechatroner.rainbow-csv",
		"mhutchie.git-graph",
		"mosapride.zenkaku",
		"ms-python.python",
		"oderwat.indent-rainbow",
		"shardulm94.trailing-spaces",
		"streetsidesoftware.code-spell-checker"
	]
}
```

```Dockerfile: Dockerfile
FROM ubuntu:22.04

ENV TZ=Asia/Tokyo
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

RUN apt-get update && apt-get install -y \
    python3.10\
    python3-pip\

RUN echo -n '\n\
    alias python="python3"\n\
    alias pip="pip3"\n\
    \n\
    ' >> /root/.bashrc

COPY ./requierments.txt /tmp/

RUN python3 -m pip install -r /tmp/requierments.txt

WORKDIR /workspace/
```

```text: requirements.txt
numpy
opencv-python
pandas
```

# :notepad_spiral: 構成ファイルの詳細解説メモ

## compose.yaml

https://docs.docker.jp/compose/toc.html

### services

1つのコンテナを1つの`service`として記述する
今回の場合は`app`と命名

複数のコンテナを定義し実行することができる（そのために使われることが多いが今回は1つ）

:::note warn
Remote-containerで開くためには、devcontainer.jsonの`service`で指定した`service`（今回の場合`app`）に`command: sleep infinity`を入れる必要がある <sup>[2.](#pray-引用参考文献)</sup>
:::

docker-compose.ymlでは`context`で上位ディレクトリを参照可能  
.devcontainer下に置いて親ディレクトリにあるDockerfileを参照させた <sup>[3.](#pray-引用参考文献)</sup>

### build

`context`で指定したパスにある`dockerfile`で指定したDockerfileをビルドする

### volumes

マウントするファイルやディレクトリを指定する。

マウントしたファイル、ディレクトリがコンテナ内で受けた編集はホストの同ファイル、ディレクトリにも反映される

:::note warn
コンテナ内で作成したファイルに関して <sup>[4.](#pray-引用参考文献)</sup>

- Linux版Dockerの場合
オーナーが`root`(UID=0)となりホスト側からは`sudo`をつけないと編集できない
- Docker Desktop for Macの場合
コンテ内ではUID=0、ホスト側ではmacOS側のUIDとなるためそのまま編集可能
:::

後述のDockerfileのADD, COPYのみで追加されたファイル等への編集はホストの同ファイルに反映されない

<details><summary>🛤️脱線メモ　compose.yamlとdocker-compose.ymlの違い</summary><div>

:::note
Docker Compose V2がリリースされて以降`compose.yaml`が推奨
:::

`--file`オプションを指定せずに`docker compose up`を実行したときに作業ディレクトリにある`compose.yaml`が読み込まれる
後方互換を維持するため`docker-compose.yml`もサポートされてはいる <sup>[5.](#pray-引用参考文献)</sup>

</div></details>

<details><summary>🛤️脱線メモ　VolumesとBind mounts</summary><div>

Dockerコンテナで扱うデータを永続化する方法には、Volumes(ボリューム)とBind mounts(バインドマウント)がある <sup>[6.](#pray-引用参考文献)</sup>

違いはDockerによって管理されるか否か　→ Volumes: 管理される　Bind mounts: 管理されない

Volumesには、Named volume(名前付きボリューム)とAnonymous volume(匿名ボリューム)がある

Anonymous volumeは、volumeの名前を指定しなかった場合にユニークなハッシュ値が割り振られる

:::note
Docker公式ではVolumesの使用を推奨 <sup>[7.](#pray-引用参考文献)</sup>

何のためのvolumeかがわかるようにNamed volumeの使用を推奨 <sup>[8.](#pray-引用参考文献)</sup>
:::

:::note warn
今回のベストプラクティスではBind mountsを使用している

将来的にgithubで開発環境とプロジェクトのソースコードを共有、管理したいため
（本番環境を用意した開発までは想定していない）
:::

ただ、上記想定においてもNamed volumeを使える方法があれば変更したい！　【情報求む】

</div></details>

## devcotainer.json

https://code.visualstudio.com/docs/devcontainers/containers

### dockerComposeFile

使用する`compose.yaml`のパスを指定する

### service

`compose.yaml`で作成した`services`のうちdevcontainerので使用する`service`を指定する

### workspaceFolder

devcontainer起動時に開かれるパスを指定する

### extensions

使用したいVSCodeの拡張機能を追加する

:::note
入れたい拡張機能を検索、該当のものを右クリックし`Copy Extension Id`を選択し、`devcotainer.json`の`extensions`にカンマ区切りで追記する
:::

おすすめの拡張機能は下記サイト等を参照

https://qiita.com/yamaguchi2000/items/76060c08764ce4c704f9

https://qiita.com/momotar47279337/items/73157407ae824751afc4

## Dockerfile

https://docs.docker.jp/engine/reference/builder.html

### FROM

ベース・イメージを指定する

:::note
- ubuntuを使うのが良いそう
:::

よく使われるalpineは軽量のOSのため、vim入っていない、互換性、速度が遅くなる等の問題がある <sup>[9.](#pray-引用参考文献)</sup>

### ENV TZ=Asia/Tokyo

タイムゾーンを指定する

:::note warn
ベース・イメージにubuntuを選択するとDocker build中にタイムゾーンの設定でハングアップするエラーを回避するため設定が必要 <sup>[10.](#pray-引用参考文献)</sup>
:::

### RUN 

コマンドを実行する

`python3.10`と`pip3`をインストールする

```Dockerfile
RUN apt-get update && apt-get install -y \
    python3.10\
    python3-pip\
```

コマンドで`python`と打った際に`python3`を、`pip`と打った際に`pip3`実行するように設定する

```Dockerfile
RUN echo -n '\n\
    alias python="python3"\n\
    alias pip="pip3"\n\
    \n\
    ' >> /root/.bashrc
```

`requierments.txt`に書かれたライブラリをインストールする

```Dockerfile
RUN python -m pip install -r /tmp/requierments.txt
```

### ADD, COPY

ホスト（ローカル）環境のファイルをコンテナ環境内に追加する

ADDとCOPYの違いは？ <sup>[11.](#pray-引用参考文献)</sup>

- リモートからのファイル追加ができるか否か　→ ADD: できる　COPY: できない
- 圧縮ファイルが自動解凍されるか否か　　　　→ ADD: される　COPY: されない

:::note
Imageサイズとセキュリティの観点からCOPYが良いそう <sup>[12.](#pray-引用参考文献)</sup>
:::

:::note warn
Docker Desktop for Macは、ADD, COPY時に上位ディレクトリを参照する相対パス`..`や`../`を消してしまうので注意 <sup>[13.](#pray-引用参考文献)</sup>
:::

### WORKDIR

`Docker run`でコンテナを立ち上げた際に開かれるパスを指定するが、devcontainer起動時は`devcontainer.json`の`workspaceFolder`で指定したパスのどちらが優先されるかは不明

<details><summary>🛤️脱線メモ　aptとapt-getの違い</summary><div>

aptはapt-getの持っていた設計上のミスを克服している
→Ubuntuではatpの使用を推奨 <sup>[14.](#pray-引用参考文献)</sup>

:::note warn
コマンドラインで打つときはaptを使うが、スクリプトで使うときは下位互換性を保ってapt-getやapt-cacheを使うことを推奨  
→Dockerfileではapt-getの使用を推奨 <sup>[15.](#pray-引用参考文献)</sup>
:::

apt-getのオプション`-y`問い合わせがあった場合はすべて「y」と答える <sup>[16.](#pray-引用参考文献)</sup>

</div></details>

<details><summary>🛤️脱線メモ　pipとpip3の違い</summary><div>

pipはPython2, Python3で使用可能、pip3はPython3でのみ使用可能  
→Python3環境へのパッケージインストールであれば明示的にpip3コマンドを利用することを推奨 <sup>[17.](#pray-引用参考文献)</sup>

</div></details>

## requirements.txt

`pip install`でインストールしたいライブラリを書く

バージョン指定も可能 <sup>[18.](#pray-引用参考文献)</sup>

:::note
Mac環境ではDockerfileで上位ディレクトリを参照できないため、
requierments.txtはDockerfileと同じ階層に置く必要がある <sup>[13.](#pray-引用参考文献)</sup>
:::
 
# :checkered_flag: さいごに

ここまでお読みいただきありがとうございます。
本記事では、devcontainerを使ったPython開発環境の自分なりベストプラクティスを紹介させていただきました。

初めて記事を書いてみて改めて実感したのは、様々な記事を書かれてきた方々の偉大さとありがたさです！

少しでもその感謝をお返ししたい気持ちを込め、『引用・参考文献』には参照したサイトはもちろんのこと、作者さまの他の記事を参照できるホーム画面へのリンクも掲載させていただきました。
番号は本記事内の参考箇所へのリンク、記事タイトルは参照したサイトへのリンク、@以降は作者さまのハンドルネームで作者さまの他の記事を参照できるホーム画面へのリンクを埋め込んでいます。

本記事の内容をより詳しくみられたい方は参考記事を、新しい発見をされたい方は参考作者さまの他の記事を見に行ってみてください！


さいごに、本記事の単語や表現に誤用があればコメントいただけると嬉しいです！
そういうの気になる人種なんでw

# :pray: 引用・参考文献

[1](#hatching_chick-はじめに). [Markdown記法 チートシート](https://qiita.com/Qiita/items/c686397e4a0f4f11683d) @[Qiita](https://qiita.com/Qiita)
[2](#services). [Docker Compose な開発環境にちょい足し3分で作るVSCode devcontainer](https://zenn.dev/saboyutaka/articles/9cffc8d14c6684?utm_source=pocket_mylist) @[saboyutaka](https://zenn.dev/saboyutaka)
[3](#services). [Dockerで上位の階層をCOPYしたい時はビルドコンテキストの設定を変更する](https://zenn.dev/mstn_/articles/790d56947a7a0d) @[ましろ](https://zenn.dev/mstn_)
[4](#volumes). [DockerのUID/GIDあれこれ: Linux版通常モード & Docker Desktop for Mac編](https://qiita.com/yuyakato/items/b4ea7e123cd47b22b2c7) @[yuyakato](https://qiita.com/yuyakato)
[5](#volumes). [Docker Compose V2で変わったdocker-compose.ymlの書き方](https://zenn.dev/miroha/articles/whats-docker-compose-v2) @[kagamirror](https://zenn.dev/miroha)
[6](#volumes). [Docker の Volume がよくわからないから調べた](https://qiita.com/aki_55p/items/63c47214cab7bcb027e0) @[aki_55p](https://qiita.com/aki_55p)
[7](#volumes). [Good use cases for volumes](https://docs.docker.com/storage/#good-use-cases-for-volumes) @[Docker](https://github.com/docker)
[8](#volumes). [dockerコンテナのデータを永続化する方法](https://tech-blog.rakus.co.jp/entry/20200331/docker) @[RAKUS Developers Blog | ラクス エンジニアブログ](https://tech-blog.rakus.co.jp)
[9](#from). [完璧なPython Dockerfileを作る(前半）](https://gavin-zhou.medium.com/完璧なpython-dockerfileを作る-前半-3b838a7ea082) @[gavin.zhou](https://gavin-zhou.medium.com)
[10](#env-tzasiatokyo). [Docker Buildでタイムゾーン設定で固まる](https://northshorequantum.com/archives/dockerbuild_tz_hang.html) @[量子プログラミング入門→量子コンピューティングサービス構築入門](https://northshorequantum.com)
[11](#add-copy). [Dockerfile の ADD と COPY の違いを結論から書く](https://qiita.com/YumaInaura/items/1647e509f83462a37494) @[YumaInaura](https://qiita.com/YumaInaura)
[12](#add-copy). [DockerfileにてなぜADDよりCOPYが望ましいのか](https://qiita.com/momotaro98/items/bf34eef176cc2bdb6892) @[momotaro98](https://qiita.com/momotaro98)
[13](#add-copy). [Docker Desktop for Mac は相対パス指定を消している](https://note.milldea.com/posts/docker-for-mac-remove-relative-path) @[Takenobu Endo (遠藤 武宣)](https://note.milldea.com/author/takenobu)
[14](#workdir). [apt と apt-get の違い](https://rs-techdev.com/archives/151) @[rs-techdev](https://rs-techdev.com)
[15](#workdir). [Dockerfileを書いていて【apt】と【apt-get】【apt-cache】の使い分けに悩んだ話](https://blog.pi-ya.com/2021/06/post_1261/) @[ぴーや！](https://blog.pi-ya.com)
[16](#workdir). [【 apt-get 】 パッケージを取得してインストール/アップデートする](https://xtech.nikkei.com/it/article/COLUMN/20060227/230709/) @[日経XTECH](https://xtech.nikkei.com)
[17](#workdir). [pipとpip3の違いを調べた](https://qiita.com/AoyaHashizu/items/435b2a01955f4a7c5f10) @[AoyaHashizu](https://qiita.com/AoyaHashizu)
[18](#requirementstxt). [Python, pipでrequirements.txtを使ってパッケージ一括インストール](https://note.nkmk.me/python-pip-install-requirements/) @[note.nkmk.me](https://note.nkmk.me)