jupyter/datascience-notebookなどのJupyterのDockerのTips

[jupyter/scipy-notebook](https://hub.docker.com/r/jupyter/scipy-notebook)や、[jupyter/datascience-notebook](https://hub.docker.com/r/jupyter/datascience-notebook/)など、JupyterをDockerで使える([Jupyter Docker Stacks](https://jupyter-docker-stacks.readthedocs.io/en/latest/))についてまとめてみます。

このdockerイメージは、例えば普段使っているPCはWindowsやMacだけど、Linuxでしか動かないモジュールを使いたい人や、データ分析のタスクで、本番環境と開発環境を同じにしつつJupyterで開発したい人などはよく使っていると思います。ただこのdockerイメージは少し癖が強くデフォルトの状態で起動すると`apt-get`すらできないとか使い方がよくわからない部分があると思います。そこでこの記事で個人的にTipsをまとめてみました。

また私が使っているDocker + VSCode + Remote Containerのリポジトリもアップします。
[SolKul/vscode-jupyter-container2](https://github.com/SolKul/vscode-jupyter-container2)

既にJupyteのDocker([Jupyter Docker Stacks](https://jupyter-docker-stacks.readthedocs.io/en/latest/))についての記事やサイトは山程出ています。まずそれらについてまとめ、そこにかかれていない私が役に立つと思うポイントをその後にまとめようと思います。

**2022/6更新**  
デフォルトのサーバーバックエンドがjupyter serverに切り替わった問題に対処

# JupyterのDockerについての過去の記事や参考サイト

[Dockerで基本的なData Science環境(Jupyter, Python, R, Julia, 定番ライブラリ)を構築する。](https://qiita.com/y4m3/items/c2703d4e131e05084b7b)
この記事でパスワードの設定や、ポートフォワーディング、ボリュームの共有など基本的なことはすべて抑えられると思います。ただ、この記事はコンテナの起動をコマンドラインで行っています。dockerの知識がないうちはこれでdockerに慣れるのもいいですが、毎回この長いコマンドを打つのも大変です。慣れてきたら`Dockerfile`+`docker-compose`を使ったほうが効率的です。

[Docker + VSCode + Remote Containerで作る快適Jupyter Lab(Python)分析環境](https://qiita.com/sho-hata/items/02ad47f67bce6816a69a)
この記事はVS CodeのRemote Containerでコンテナを動かしています。VS Codeであればファイルの配置もグラフィカルに表示され楽ですし、ファイルの中身もテキストエディタで編集できます。`docker-compose`も使っているので毎回コマンドを打つ必要がないです。何よりもVS Codeの強力なデバック機能や拡張機能が使えるのは魅力的です。


# Jupyter Docker Stacksについてメモ

以下、[Jupyter Docker Stacks](https://jupyter-docker-stacks.readthedocs.io/en/latest/)を使う際のTipsをまとめます。

## `docker run`より`docker-compose`を使う

dockerに慣れてきたら`docker-compose`を使うほうが断然楽です。次の`docker run`コマンドと、その下の`docker-compose.yml`は同じ働きをします。

```bash
docker run  \
    --user root \
    -e GRANT_SUDO=yes \
    -e TZ=Asia/Tokyo \
    -p 8888:8888 \
    --name notebook \
    -v ./work:/home/jovyan/work \
    jupyter/datascience-notebook \ 
    start-notebook.sh \
    --NotebookApp.password='sha1:YOUR_PASSWORD_HASH_VALUE'
```

```docker-compose.yml
version: '3'
services:
  notebook:
    image: jupyter/datascience-notebook
    user: root
    ports:
      - '8888:8888'
    environment:
      - GRANT_SUDO=yes
      - TZ=Asia/Tokyo
    volumes:
      - ./work:/home/jovyan/work
    command: start-notebook.sh --NotebookApp.password='sha1:YOUR_PASSWORD_HASH_VALUE'
```

コマンドだと間違ってEnterを押してしまった時点で起動してしまいますが、`docker-compose.yml`であれば落ち着いて編集し、設定が正しいか確認したうえでコンテナを立ち上げることができます。


まず、次のコマンドで、`docker-compose.yml`の設定が正しいか検証でき、また(上記の`docker-compose.yml`ではホスト環境変数は使っていませんが)設定したホスト環境変数に正しく値が代入されるかをチェックすることができます。

```shell-session
# docker-compose config
```

そして立ち上げる場合は`docker-compose up`とすればいいだけです。

`docker run`のどのオプションが`docker-compose.yml`のどの項目に対応しているかなどは
[複数のDockerコンテナを自動で立ち上げる構成管理ツール「Docker Compose」（Dockerの最新機能を使ってみよう：第7回）](https://knowledge.sakura.ad.jp/5736/)
などを参照してください。

また、`docker run`コマンド`-e`オプションや、`docker-compose.yml`の`environment`でコンテナ中に環境変数を設定しています。
`docker run`の`-e`オプションについては公式リファレンスを参照してください。
[run — Docker-docs-ja 19.03 ドキュメント](https://docs.docker.jp/engine/reference/commandline/run.html)
`docker-compose`の環境変数についても公式リファレンスを参照するか、私が過去まとめた記事があるのでそちらを参照してください。
[docker-composeのenv_fileと.envファイルの違い](https://qiita.com/SolKul/items/989727aeeafcae28ecf7)

また、`.env`ファイルを追加し、`docker-compose.yml`を適切に書き換えればプロキシ下などでも使えます。詳しくは[docker-composeでのプロキシ設定を一つのファイルにまとめる](https://qiita.com/SolKul/items/2561c62734958c231b24)を参考にしてください。

## VSCode+Remote Containerで使う
さらに言えば、VSCode+Remote Containerで使えば更に快適になります。上で述べたように、VSCodeであれば使うファイルの配置もグラフィカルに表示されるので、例えば、分析対象のデータを配置するときもわかりやすいです。ファイルの中身もテキストエディタで編集できます。ポートフォワーディングも後からできます。`docker-compose.yml`も使っているので毎回コマンドを打つ必要がないです。何よりもVSCodeの強力なデバック機能や拡張機能が使えるのは魅力的です。なんならipynbファイルもそのまま開けます。

私がVSCode+Remote Containerで使う場合の例を[SolKul/vscode-jupyter-container2](https://github.com/SolKul/vscode-jupyter-container2)にアップしました。

VSCodeをインストールし、拡張機能のRemote Containersをインストールしてください。そしてVSCodeで適当なフォルダを開いて、このリポジトリをチェックアウトし<kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>p</kbd>でコマンドパレットを開き、「Remote-Containers: rebuild and Reopen in Container」を検索して<kbd>Enter</kbd>を押すことで、VSCodeが`docker-compose.yml`を参照してコンテナを立ち上げ、中に入ってくれます。

このリポジトリのファイル構造と各ファイルは次のようになります。

```
/
　├ .devcontainer/
  │ ├ .env
  │ ├ devcontainer.json
  │ ├ docker-compose.yml
  │ └ Dockerfile
　└ work/ ※1
```

```docker-compose.yml
version: '3.3'

services:
  jupyter_dev: # jupyterコンテナのサービス名※2
    build: . # ビルド対象のDockerfileが同じフォルダ内にあるためピリオド(.)を打つ
    environment:
      - GRANT_SUDO=yes
      - DOCKER_STACKS_JUPYTER_CMD=notebook
    working_dir: /home/jovyan/work
    user: root
    volumes: # ホストとのボリューム共有。../workは上のフォルダ構造で示した※1の一つ上の階層のworkフォルダを指し示す。
      - type: bind
        source: ../work
        target: /home/jovyan/work
    command: start-notebook.sh --NotebookApp.token=''
```

ポートフォワーディング下のようにVSCode上で後からできるの`docker-compose.yml`にはポートフォワーディングの設定は書いてません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/466677/0f425e7d-1cdb-7824-8e52-1a863be84818.png)

```devcontainer.json
{
    "name": "jupyter-devcontainer-project",
    "dockerComposeFile": [
        "./docker-compose.yml"
    ],
    "service": "jupyter_dev", //サービス名はdocker-compose.yml内のサービス名※2と一致させる
	"extensions": [
		"ms-python.python"
    ],
    "workspaceFolder": "/home/jovyan/work"
} 
```

```Dockerfile:Dockerfile
# pandas、numpy、scipy、 scikit-image、matplotlibなど一通り入っているjupyter/scipy-notebookを使う。
# また、常にpullするimageを固定したいので、一意のtagをつける。
FROM jupyter/scipy-notebook:44fe185f9495
USER jovyan
RUN conda install -y jupyter_contrib_nbextensions
```

VSCode+Remote Containerの詳しい使い方については「VSCode Remote Container」と検索するか、[公式リファレンス(英語)](https://code.visualstudio.com/docs/remote/containers)を参考にしてください

## bashを起動したい場合
新しいモジュールをインストールしたり、ファイル操作や`apt-get`などbashを操作したい場合があると思います。その場合は`docker ps`でコンテナIDを調べ、

```bash
docker exec -it コンテナID /bin/bash
```

などすれば、bashを起動できます。またはVScodeでRemote containerしている場合は新しくターミナルを開けばいいです。

`docker ps`や`docker exec`などのコマンドがわからない場合は
[Docker ハンズオン - 基本コマンド編](https://qiita.com/hihihiroro/items/6dda871dc2566801a6da)
などを参照してください。

ただ、デフォルトで使っている場合、ユーザーはjovyanになり、権限がないので`apt-get update`や`apt-get install`はできないです。これについては次で述べます。

## ユーザーと権限について

ユーザーは管理者であるrootと、jupyter notebookを実行するjovyanがいます。ブラウザでアクセスでき、ファイルを編集できるjupyter notebookを管理者権限で実行するのはセキュリティ的にまずいので、jupyter notebookはjovyanで起動するようになっています。

デフォルトではユーザーはjovyanになります。なのでデフォルトの状態で

```bash
docker exec -it コンテナID /bin/bash
```

とbashを実行しても、ユーザーはjovyanとなり、権限がないので`apt-get update`や`apt-get install`はできません。

しかし`docker exec`コマンドの`--user`オプションを使えばrootでbashを起動できます。
https://docs.docker.jp/engine/reference/commandline/exec.html

```bash
docker exec -it --user root コンテナID /bin/bash
```
これで`apt-get`などが使えるようになります。

また、`Dockerfile`中で最後の行で以下を追加する、

```Dockerfile
USER root
```

もしくは、`docker-compose.yml`で

```docker-compose.yml
    user: root
```

とコンテナ起動ユーザーをrootに切り替えた場合、デフォルトでbashを開くとユーザーはrootで起動し、同じく`apt-get`などが使えるようになります。先程の`docker exec`コマンドの`--user`オプションを使う場合と比べて、`Dockerfile`や`docker-compose.yml`でrootで設定した場合、VSCodeでRemote Containerで開発している場合に、VSCode上で`Terminal>New Terminal`で新しいターミナルを起動した際に、rootでbashが起動するようになるので、VSCodeを使っている場合はこちらのほうが便利です。また、コンテナ起動ユーザーをrootにしても、jupyter notebookはjovyanで起動するので安心してください。

rootユーザーから、`su jovyan`でjovyanに切り替えることができますが、このjovyanでは`/opt/conda/bin/`のパスが通っていないため、`pip`コマンドや`conda`コマンドが使えまえん。`export PATH=$PATH:/opt/conda/bin`などとして、環境変数の`PATH`に`/opt/conda/bin/`を加えれば、`pip`コマンドや`conda`コマンドが使えるようになります。

また、同じくコンテナ起動ユーザーをrootに切り替えた上で、環境変数`GRANT_SUDO`に`yes`を指定、つまり`docker-compose.yml`で、

```docker-compose.yml
environment:
    - GRANT_SUDO=yes
```

としていれば、`su jovyan`としjovyanに切り替えた後のjovyanは`sudo`が使えるようになり、`sudo apt-get`などができます。

つまり`GRANT_SUDO=yes` でかつコンテナ起動ユーザーがrootならjovyanは`sudo`を使えるということです。詳しくは、

https://jupyter-docker-stacks.readthedocs.io/en/latest/using/recipes.html

を参照してください。

## 起動時のコマンドについて

[jupyter/datascience-notebook](https://hub.docker.com/r/jupyter/datascience-notebook/)など [Jupyter Docker Stacks](https://jupyter-docker-stacks.readthedocs.io/en/latest/)のDockerコンテナは立ち上がると同時にデフォルトで`start-notebook.sh`が走り、各種設定を行います。(`GRAT_SUDO`の有効化など)。`start-notebook.sh`が必要な設定を行うので、`Dockerfile`の`CMD`コマンドや`docker-compose`の`command`設定で、起動時のコマンドを`start-notebook.sh`以外のコマンドを変更するのは避けたほうがいいです。

また、`GRAT_SUDO`の有効化やjovyanのユーザーID、グループIDの変更など`start-notebook.sh`が行ういくつかの設定はコンテナ起動ユーザーがrootでないと有効になりません。

詳しくは以下のページなどを参照してください。  
[Common Features — Docker Stacks documentation](
https://jupyter-docker-stacks.readthedocs.io/en/latest/using/common.html#user-related-configurations)

## Jupyter notebookを使うための設定

[Jupyter Docker Stacks](https://jupyter-docker-stacks.readthedocs.io/en/latest/index.html)は2022年1月ごろ、デフォルトのサーバーバックエンドをjupyter notebookからjupyter serverに切り替えました。これに伴いデフォルトのエディタがjupyter notebookからjupyter Labに切り替わりました。

バックエンドがjupyter serverのままでも、jupyter notebookは引き続き使えます。`loacalhost:8888`にアクセスすると、`jupyter lab`に、`loacalhost:8888/tree`にアクセスすると、従来の`jupyter notebook`にアクセスします。


しかし、様々な拡張機能があるjupyter_contrib_nbextensionsはjupyter notebookでしか使えず、しかもバックエンドがjupyter serverだと`/tree`のnotebookからもnbextensionは使えなくなってしまいます。私は選択中の文字を強調する`Highlit Selected Word`を使いたいため、jupyter notebook+jupyter_contrib_nbextensionsを使い続けています。いまのところ同じ機能はjupyter labは対応していないようです。

なのでバックエンドをjupyter notebookに切り替える必要があります。


[Common Features — Docker Stacks documentation](https://jupyter-docker-stacks.readthedocs.io/en/latest/using/common.html#switching-back-to-the-classic-notebook-or-using-a-different-startup-command)によるとコンテナ内の環境変数`DOCKER_STACKS_JUPYTER_CMD`を`notebook`に設定することで、立ち上がったサーバのバックエンドjupyter notebookに切り替えることができます。なので、`docker-compose.yml`中で次のように設定しています。

```docker-compose.yml
environment:
    - DOCKER_STACKS_JUPYTER_CMD=notebook
```


## モジュールのインストールとユーザー権限について

rootで`conda install`などでモジュールを管理者権限でインストールしてしまうと、jupyter notebookを実行するjovyanユーザーで不具合が起きるように思えますが、使っていて、`conda install`や`pip install`などモジュールのインストールをrootとjovyanどちらのユーザーで実行しても、ほぼほぼ、Pythonのプログラミングには問題ないように感じます。

ただ、`git clone`などファイル、フォルダを生成する場合は、jovyanで行ったほうがいいです。

rootで

```bash
root $ git clone リポジトリ
```
としてしまうと、クローンしたリポジトリのファイル、フォルダの書き込み権限がroot権限になってしまいまい、その後不具合を起こす可能性があります。こうしてしまった場合は、`chown`で所有者を書き換えましょう。

また、`jupyter_contrib_nbextensions`のインストールもjovyanで行ったほうがいいです。

`git clone`と同様にrootで

```bash
root $ conda install -y jupyter_contrib_nbextensions
```

としてしまうと、`nbextensions`関連のファイルがroot権限になってしまいます。`jupyter`上にnbextensionsのタブが現れはしますが、`jupyter`起動ユーザーがjovyanなため、その状態でどのエクステンションを有効にしても実際は権限の問題で`/home/jovyan/.jupyter/nbconfig/`以下に存在する設定ファイルが書き換わらずに、F5で更新すると無効になってしまいます。

なので、`jupyter_contrib_nbextensions`のインストールは次のように`Dockerfile`内でjovyanユーザーで予めやっておいたほうがいいです。

```Dockerfile
USER jovyan
RUN conda install -y jupyter_contrib_nbextensions
```

詳しくは
[jupyter notebookが使えるdockerにnbextensionsが導入できない](https://teratail.com/questions/193568)

を参照してくださいい。