DockerイメージにLinuxbrewをDockerfileでインストールする

## はじめに
自分のパソコンでは環境が出来上がっている状態で
みんなで環境構築をやろう！　教えよう！　となったとき。
みなさんならどうするでしょうか。

細かい差異も見逃さないために、自分のパソコンに入っているパッケージをアンインストールして、環境構築の実演をしてみせるでしょうか。

（自分のパソコンの環境をいじるのは面倒くさいので）細かいことは気にせず、手順書を作って見守る（適宜困ったことがあったら助ける）でしょうか。

今回は、「自分の環境を汚したくないけど、確実に環境構築をやってもらうために実演をしたい」ってときに`Dockerコンテナ`使えないかなーと色々調べてみた結果は紹介させてもらいます。
※ あまり実践的ではないです。

## 知れること・知れないこと

※ 結果的にやっていることはubuntu18.04のイメージに`Linuxbrew`をDockerfileでインストールする方法になります。

  
**知れること**

- ubuntuのイメージにLinuxbrewをDockerfileを使ってインストールする方法
- 素人目線のDockerfileの書き方のポイント

**知れないこと**

- windowsで環境構築を実演する際のDockerイメージの作り方

## 実現方法の考え方
おおよそmacでは`Homebrew`使って、パッケージインストールして、環境構築等するだろうという~~偏見~~仮定の元、
「LinuxbrewをLinuxのDockerイメージに入れたら、見かけ上はmacでHomebrew使うのと変わらなくない？　ほらコマンドもbrewで変わらないし……」
という安直な考えで、`Linuxbrew`の利用を考えます。
### Linuxbrew

`Linuxbrew`とは`Homebrew`のLinux版（名前そのまま）です。
2019年2月から本家 Homebrew 2.0.0 にてLinuxbrewが正式にサポートされるとのことなので（[homebrew-2.0.0](https://brew.sh/2019/02/02/homebrew-2.0.0/)）、使わない手はないです。

参考
[LinuxbrewでUbuntu18.04のパッケージ管理](https://qiita.com/tikogr/items/e19c2a2cec41a8d4d85f)
[Linuxbrewのススメ](https://qiita.com/thermes/items/926b478ff6e3758ecfea)

## 目標
作成したイメージのDockerコンテナに入って

```console
$ brew doctor
```
とコマンドを叩いて、一発で`Your system is ready to brew.`と出てくることを目指します。

## ホームディレクトリにLinuxbrewをインストールするDockerfile

こちらは道半ばバージョンです。
Linuxbrewは`/home/linuxbrew/.linuxbrew`下にインストールすることが推奨されています。
上記デフォルトのディレクトリにインストールすることで、ビルド済みバイナリがあるパッケージはバイナリをインストールできるようになります。
そのためインストール場所がデフォルトでないと`brew doctor`でWarningが出ます。

ただ、Dockerfileで`/home/linuxbrew/.linuxbrew`配下にLinuxbrewをインストールするには、私が試した限り、少々特殊なことをする必要がありました。
こちらはこちらで「素直にDockerfileを書いたバージョン」として、記載しておきたいと思います。

こちらの方法だと`/home/[ユーザ名]/.linuxbrew/`にインストールされることになります。Warningが出るだけでLinuxbrewは普通に利用可能です。

```Dockerfile
FROM ubuntu:18.04
LABEL maintainer beeeeyan
#環境変数を設定
ENV DEBIAN_FRONTEND=noninteractive
ENV USER beeeeyan
ENV HOME /home/${USER}
ENV SHELL /bin/bash
ENV PW password

# 種々インストール
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
    vim \
    sudo \
    locales \
    build-essential \
    ca-certificates \
    curl \
    file \
    git && \
    # 一般ユーザーアカウント追加
    useradd -m ${USER} && \
    # 一般ユーザーにsudo権限を付与
    gpasswd -a ${USER} sudo && \
    # 一般ユーザーのパスワードを設定
    echo "${USER}:${PW}" | chpasswd && \
    # ログインシェルを指定
    sed -i.bak -r s#${HOME}:\(.+\)#${HOME}:${SHELL}# /etc/passwd && \
    #　localの設定
    locale-gen en_US.UTF-8

# コマンドを実行するUSERを変更
USER ${USER}
# 作業ディレクトリを指定
WORKDIR ${HOME}

# Linuxbrewをインストール
RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/Linuxbrew/install/master/install.sh)" && \
# pathの設定
    echo 'export PATH=${HOME}/.linuxbrew/bin:$PATH' >> .bash_profile
```

### 説明
素人目線のDockerfileの書き方にも触れています。
今回の「Linuxbrewのインストール」に深く関わる箇所には気持ち最初に☆付けました。

```Dockerfile
LABEL maintainer beeeeyan
```
MAINTAINER(作成者情報)はDocker 1.13から非推奨（deprecated）となっているそうです。LABELで書きましょう。
参考 : [1.13以降はMAINTAINERの代わりにLABELを使うようにしよう](https://qiita.com/okisanjp/items/a43068f956d273703ea8)

  
```Dockerfile
ENV DEBIAN_FRONTEND=noninteractive
```
`brew doctor`で出てくるエラーを潰す仮定で`locales`をインストールすることにしたのですが（後述）、`locales`をインストールしようとすると`apt-get install`自体が止まったので設定しました。詳しくは以下参照ください。
参考 : [Docker Ubuntu18.04でtzdataをinstallするときにtimezoneの選択をしないでinstallする](https://qiita.com/yagince/items/deba267f789604643bab)

  
```Dockerfile
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
```
ここいらへんは呪文ですね。`--no-install-recommends`の意味を知らなかったのですが「指定したもの以外余計なものは入れない」ということみたいです。
参考 : [#Linux #Ubuntu #docker #Dockerfile のこれは何？ apt-get install --no-install-recommends](https://qiita.com/YumaInaura/items/2cc1b64f875a36777f2d)


```Dockerfile
    vim \
    sudo \
    locales \
    build-essential \
    ca-certificates \
    curl \
    file \
    git && \
    ~省略~
　　 #　localの設定
    locale-gen en_US.UTF-8
```

- `vim`と`sudo`
あると便利かな、くらいで入れています。`sudo`に関しては「完成版」では必須です。

- `locales`…（省略）…`locale-gen en_US.UTF-8`
`brew doctor`とコマンド叩いて、以下のようなエラーが出たのでインストール&設定しました。
>warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8): No such file or directory …(省略)

- ☆ `build-essential`と`ca-certificates`と`curl`と`file`と `git `
調べてもらったらわかりますが、`build-essential`と`curl`と`file`と `git `については、Linuxbrewをインストールするときに公式で先にインストールするよう指定されているパッケージです。
ポイントは **ca-certificates**を指定するところにあります。（これがないと`curl`などを使っても通信ができません）このパッケージは**普段は自動でインストールされるもの**です。勘のいい方はお分かりかと思いますが`apt-get install`に`--no-install-recommends`を指定した影響で明示的に書いておく必要が出てきたパッケージです。
参考 : [公式](https://docs.brew.sh/Homebrew-on-Linux)

```Dockerfile
    # 一般ユーザーアカウント追加
    useradd -m ${USER} && \
    # 一般ユーザーにsudo権限を付与
    gpasswd -a ${USER} sudo && \
    # 一般ユーザーのパスワードを設定
    echo "${USER}:${PW}" | chpasswd && \
    # ログインシェルを指定
    sed -i.bak -r s#${HOME}:\(.+\)#${HOME}:${SHELL}# /etc/passwd && \
```
☆ 一般ユーザを追加している部分
ユーザを追加する理由は**rootユーザでLinuxbrewをインストールしようとするエラーになるから**です。参考先の情報から`sed`コマンドの書き方は多少調整しました。
参考 : [Dockerで開発環境を仮想化する](https://qiita.com/Riliumph/items/3b09e0804d7a04dff85b)

```Dockerfile
# コマンドを実行するUSERを変更
USER ${USER}
# 作業ディレクトリを指定
WORKDIR ${HOME}
```
コマンドを実行するユーザとディレクトリを作成したユーザに変更しています。


後は[公式](https://docs.brew.sh/Homebrew-on-Linux)のインストールコマンド叩いて`.bash_profile`にLinuxbrewのパス（今回だと`/home/[ユーザ名]/.linuxbrew/bin`）を指定するだけです。

### なぜ「素直な書き方」では`/home/linuxbrew/.linuxbrew`にインストールできないのか？

素直な書き方だとビルド中に以下のような応答が求められます。

```console
==> Select the Homebrew installation directory
- Enter your password to install to /home/linuxbrew/.linuxbrew (recommended)
- Press Control-D to install to /home/[ユーザ名]/.linuxbrew
- Press Control-C to cancel installation
```
ここで`Enter your password`できなくてつみます。
ただ不思議と`Control-D`の挙動はしてくれて、`/home/[ユーザ名]/.linuxbrew`にインストールされるという寸法です。

## [完成版]`/home/linuxbrew/.linuxbrew`にLinuxbrewをインストールするDockerfile

ほんの少しトリッキーなことをしたのですが、以下でうまくいきました。

```Dockerfile
FROM ubuntu:18.04
LABEL maintainer beeeeyan
#環境変数を設定
ENV DEBIAN_FRONTEND=noninteractive
ENV USER beeeeyan
ENV HOME /home/${USER}
ENV SHELL /bin/bash
ENV PW password

# 種々インストール
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
    vim \
    sudo \
    locales \
    build-essential \
    ca-certificates \
    curl \
    file \
    git && \
    # 一般ユーザーアカウント追加
    useradd -m ${USER} && \
    # 一般ユーザーにsudo権限を付与
    gpasswd -a ${USER} sudo && \
    # 一般ユーザーのパスワードを設定
    echo "${USER}:${PW}" | chpasswd && \
    # ログインシェルを指定
    sed -i.bak -r s#${HOME}:\(.+\)#${HOME}:${SHELL}# /etc/passwd && \
    #　localの設定
    locale-gen en_US.UTF-8 && \
    #　linuxbrewの「Alternative Installation」を実行
    git clone https://github.com/Homebrew/brew /home/linuxbrew/.linuxbrew/Homebrew && \
    mkdir /home/linuxbrew/.linuxbrew/bin && \
    ln -s /home/linuxbrew/.linuxbrew/Homebrew/bin/brew /home/linuxbrew/.linuxbrew/bin && \
    eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv)

# コマンドを実行するUSERを変更
USER ${USER}
# 作業ディレクトリを指定
WORKDIR ${HOME}

# Linuxbrew関連のフォルダ作成
RUN echo ${PW} | sudo -S mkdir -p /home/linuxbrew/.linuxbrew/etc \
    /home/linuxbrew/.linuxbrew/include \
    /home/linuxbrew/.linuxbrew/lib \
    /home/linuxbrew/.linuxbrew/opt \
    /home/linuxbrew/.linuxbrew/sbin \
    /home/linuxbrew/.linuxbrew/share \
    /home/linuxbrew/.linuxbrew/var/homebrew/linked \
    /home/linuxbrew/.linuxbrew/var/homebrew/locks \
    /home/linuxbrew/.linuxbrew/Cellar && \
    # 権限変更
    echo ${PW} | sudo -S chown -R ${USER} /home/linuxbrew/.linuxbrew/etc \
    /home/linuxbrew/.linuxbrew/include \
    /home/linuxbrew/.linuxbrew/lib \
    /home/linuxbrew/.linuxbrew/opt \
    /home/linuxbrew/.linuxbrew/sbin \
    /home/linuxbrew/.linuxbrew/share \
    /home/linuxbrew/.linuxbrew/var/homebrew/linked \
    /home/linuxbrew/.linuxbrew/Cellar \
    /home/linuxbrew/.linuxbrew/Homebrew \
    /home/linuxbrew/.linuxbrew/bin \
    /home/linuxbrew/.linuxbrew/var/homebrew/locks && \
    # パスの設定
    echo 'export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH"' >> .bash_profile && \
    # パスの反映
    . ~/.bash_profile && \
    # brew doctorの実行
    brew doctor
```
ポイントは[公式](https://docs.brew.sh/Homebrew-on-Linux)の「Alternative Installation」の部分です。

```
    #　linuxbrewの「Alternative Installation」を実行
    git clone https://github.com/Homebrew/brew /home/linuxbrew/.linuxbrew/Homebrew && \
    mkdir /home/linuxbrew/.linuxbrew/bin && \
    ln -s /home/linuxbrew/.linuxbrew/Homebrew/bin/brew /home/linuxbrew/.linuxbrew/bin && \
    eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv)
```


「Alternative Installation」で説明されている内容は、任意の場所に`Linuxbrew`をインストールする際によく利用されるコマンドです。これを**デフォルトの場所にインストールする**方式で利用します。
つまり「Alternative Installation」に書かれているインストール場所`~/.linuxbrew/Homebrew`を`/home/linuxbrew/.linuxbrew/Homebrew`に変更します。（変更した状態のものが上記コマンドです）

```
# Linuxbrew関連のフォルダ作成
RUN echo ${PW} | sudo -S mkdir -p /home/linuxbrew/.linuxbrew/etc \
    /home/linuxbrew/.linuxbrew/include \
    /home/linuxbrew/.linuxbrew/lib \
    /home/linuxbrew/.linuxbrew/opt \
    /home/linuxbrew/.linuxbrew/sbin \
    /home/linuxbrew/.linuxbrew/share \
    /home/linuxbrew/.linuxbrew/var/homebrew/linked \
    /home/linuxbrew/.linuxbrew/var/homebrew/locks \
    /home/linuxbrew/.linuxbrew/Cellar && \
    # 権限変更
    echo ${PW} | sudo -S chown -R ${USER} /home/linuxbrew/.linuxbrew/etc \
    /home/linuxbrew/.linuxbrew/include \
    /home/linuxbrew/.linuxbrew/lib \
    /home/linuxbrew/.linuxbrew/opt \
    /home/linuxbrew/.linuxbrew/sbin \
    /home/linuxbrew/.linuxbrew/share \
    /home/linuxbrew/.linuxbrew/var/homebrew/linked \
    /home/linuxbrew/.linuxbrew/Cellar \
    /home/linuxbrew/.linuxbrew/Homebrew \
    /home/linuxbrew/.linuxbrew/bin \
    /home/linuxbrew/.linuxbrew/var/homebrew/locks && \
```
後半は`brew doctor`と打って怒られる部分を先回りして処理しています。
`sudo`で求められるパスワードは`echo`と`-S`オプションで乗り越え、
前半は必要なフォルダの作成・後半はフォルダの権限の変更（rootユーザでは`Linuxbrew`が利用できないので）、、といった具合です。
権限の変更はエラーでは`${whoami}`でしたが、Dockerfileの統一感を考えて`${USER}`でここでは書きました。

```
    # brew doctorの実行
    brew doctor
```
パスを追加して、最後に一回`brew doctor`を実行しておきます。
一回目の`brew doctor`では、一部`brew`のツールらしきものがインストールされます。（一発で`Your system is ready to brew`と出てこない！）

## コンテナを立ち上げるまで〜〜
ビルドします。（試行錯誤する段階では、私は[Automated Build Docker](http://docs.docker.jp/docker-hub/builds.html)でやってました）
Dockerfileがカレントディレクトリに存在する状態で以下コマンドを実行

```console
$ docker build -t [イメージ名の指定] .
```
コンテナの起動

```
$ docker run --name [コンテナ名の指定] -it -d [イメージ名] /bin/bash
```
起動中のコンテナに入る
※ちょっとした話ですが`--login`をつけておかないと`.bash_profile`に指定した設定が反映されないのでご注意ください。（パスが反映されず`brew doctor`が実行できない…）

起動中のコンテナに入る

```console
$ docker exec -it [コンテナ名] /bin/bash --login
```

`brew doctor`！
<img width="714" alt="スクリーンショット 2020-03-06 0.22.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277737/c042d8bd-aea0-8b71-a28b-c41d7dd3d665.png">

今回の目標達成です！

## 参考

- [DockerでCentOS7のコンテナを用意してLinuxbrewをインストールする](https://mabui.org/docker-centos7-linuxbrew-install/)
expectコマンドに言及されていて、私も格闘しましたが難しかったです。

- [Dockerfileを書くときに気をつけていること10選](https://qiita.com/c18t/items/f3a911ef01f124071c95)
Dockerfileの書き方の参考。

- [LinuxbrewでUbuntu18.04のパッケージ管理](https://qiita.com/tikogr/items/e19c2a2cec41a8d4d85f)
Linuxbrewについて