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)、使わない手はないです。

参考
LinuxbrewでUbuntu18.04のパッケージ管理
Linuxbrewのススメ

目標

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

$ 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は普通に利用可能です。

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のインストール」に深く関わる箇所には気持ち最初に☆付けました。

LABEL maintainer beeeeyan

MAINTAINER(作成者情報)はDocker 1.13から非推奨(deprecated)となっているそうです。LABELで書きましょう。
参考 : 1.13以降はMAINTAINERの代わりにLABELを使うようにしよう

ENV DEBIAN_FRONTEND=noninteractive

brew doctorで出てくるエラーを潰す仮定でlocalesをインストールすることにしたのですが(後述)、localesをインストールしようとするとapt-get install自体が止まったので設定しました。詳しくは以下参照ください。
参考 : Docker Ubuntu18.04でtzdataをinstallするときにtimezoneの選択をしないでinstallする

RUN apt-get update && \
    apt-get install -y --no-install-recommends \

ここいらへんは呪文ですね。--no-install-recommendsの意味を知らなかったのですが「指定したもの以外余計なものは入れない」ということみたいです。
参考 : #Linux #Ubuntu #docker #Dockerfile のこれは何? apt-get install --no-install-recommends

    vim \
    sudo \
    locales \
    build-essential \
    ca-certificates \
    curl \
    file \
    git && \
    ~省略~
   # localの設定
    locale-gen en_US.UTF-8
  • vimsudo
    あると便利かな、くらいで入れています。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-essentialca-certificatescurlfilegit
    調べてもらったらわかりますが、build-essentialcurlfilegitについては、Linuxbrewをインストールするときに公式で先にインストールするよう指定されているパッケージです。
    ポイントは ca-certificatesを指定するところにあります。(これがないとcurlなどを使っても通信ができません)このパッケージは普段は自動でインストールされるものです。勘のいい方はお分かりかと思いますがapt-get install--no-install-recommendsを指定した影響で明示的に書いておく必要が出てきたパッケージです。
    参考 : 公式

    # 一般ユーザーアカウント追加
    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で開発環境を仮想化する

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

コマンドを実行するユーザとディレクトリを作成したユーザに変更しています。

後は公式のインストールコマンド叩いて.bash_profileにLinuxbrewのパス(今回だと/home/[ユーザ名]/.linuxbrew/bin)を指定するだけです。

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

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

==> 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

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

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

ポイントは公式の「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でやってました)
Dockerfileがカレントディレクトリに存在する状態で以下コマンドを実行

$ docker build -t [イメージ名の指定] .

コンテナの起動

$ docker run --name [コンテナ名の指定] -it -d [イメージ名] /bin/bash

起動中のコンテナに入る
※ちょっとした話ですが--loginをつけておかないと.bash_profileに指定した設定が反映されないのでご注意ください。(パスが反映されずbrew doctorが実行できない…)

起動中のコンテナに入る

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

brew doctor

今回の目標達成です!

参考