dockerで自分のnvim環境をどこでも再現する


背景

21卒学生なので、これまで就職活動を兼ねて多くのインターンシップに参加しました。
大抵開発マシンを用意していただいて、初日にこれ使って好きに環境作ってねと言われることが多く、
そのたびに普段の開発環境を再現するのが面倒だった経験があります。

とくに2週間 ~ 1ヶ月くらいの期間のインターンにいくつも参加していると
「これ時間かけて構築してるけど結局使い捨てだししばらくしたらまた違う会社でやり直さないとなんだよな...」と思うことが多くしんどかったです。

自分のマシンを持ち込めればまあ楽なんですが大抵の場合は社内ポリシーによってそうもいかないので、
じゃあもうdockerイメージに固めてpullってくるだけにすれば楽じゃんというのが経緯です。

また普段から複数のマシンやクラウドのインスタンスをいくつも使い分けていて、設定をいじったときに同期するのが面倒というのもあります。

私は基本開発にコマンドラインとnvimしか使わないのでdockerで環境を作るのと相性がよかったという前提があります。

dotfileの共有じゃだめなのか

init.vimやpluginの設定を書いたtomlファイルを管理して、それをcloneしてきて必要なプラグインをインストールするのではだめなのでしょうか。
もともとは私もこの運用をしていましたが、開発環境として考えると次の点が足りず結局追加でセットアップすることになります。

  • python3など動作に必要なランタイム
  • solargraphなど補完に使うLanguage Server
  • prettier, eslintなどのformatterやLinter

これらはvimの外にあるもので、マシンごとにインストールして設定する必要があります。
ただしいつもいつも同じOSを使えるわけではないので共通化しにくく、結局リストアップして手動でインストールする運用をしていました。

これら必要なランタイムやパッケージすらもまとめてしまえば(dockerさえ使えれば)docker registryにあげておいてpullってきて即使えるので非常に楽です。
また環境差もなく完全再現できることも気楽でよいです。

参考

すでに同じようなことをされている方がいたので、こちらを参考にさせていただきました。

個人設定込みNeovimのDokcerイメージを作成した
alpineで設定込みNeovim Dockerイメージのサイズを半減させた

Dockerfile

基本的に参考記事を真似ているので、異なる部分だけ抜粋して解説します。
なお、前提として次のディレクトリ構成になっています。

リポジトリはここ
イメージはここ

.
├── Dockerfile
└── nvim
    ├── dein_lazy.toml
    ├── dein.toml
    └── init.vim

FROM alpine:latest

LABEL maintainer "Akira Shinohara <[email protected]>"


# マルチバイト文字をまともに扱うための設定
ENV LANG="en_US.UTF-8" LANGUAGE="en_US:ja" LC_ALL="en_US.UTF-8"

# 最低限必要なパッケージ
RUN apk update && \
    apk upgrade && \
    apk add --no-cache \
    build-base \
    curl \
    gcc \
    git \
    libxml2-dev \
    libxslt-dev \
    linux-headers \
    musl-dev\
    neovim \
    nodejs \
    npm \
    python-dev \
    py-pip \
    python3-dev \
    py3-pip \
    ruby \
    ruby-dev \
    && \
    rm -rf /var/cache/apk/*

RUN pip3 install --upgrade pip pynvim
RUN gem install -N \
    etc \
    json \
    rubocop \
    rubocop-performance \
    rubocop-rails \
    rubocop-rspec \
    solargraph

# install dein.vim
RUN curl -sf https://raw.githubusercontent.com/Shougo/dein.vim/master/bin/installer.sh \
    | sh -s /root/.cache/dein

COPY nvim /root/.config/nvim

RUN nvim +:UpdateRemotePlugins +qa
RUN chmod -R 777 /root

ENTRYPOINT ["nvim"]

LanguageServerやLinterなど外部ツールもまとめてインストールする

先の通り、dotfileだけでは再現が難しい使っているプラグインが要求するツールやランタイムもこのイメージにまとめてインストールしてしまいます。
そのためにruby-devやnpmなどもインストールしています。

dein.vimを使ってプラグインを管理する

rootユーザの$HOME下にinit.vimやdein.vim本体、プラグイン設定を書いたtomlを展開し、
RUN nvim +:UpdateRemotePlugins +qaでインストールしています。
なにかプラグインを足したりnvimの設定を変えた場合はdockerイメージをbuildすることで反映され、repositoryにpushすることで更新します。

/root以下のパーミッションを777にする

これは正直どうかと思うんですが、一応理由があってのことなので後ほど解説します。

実行

docker run --rm -it -u $(id -u):$(id -g) -e HOME=/root -v $HOME:$HOME --workdir=$(pwd) s10akir/moja-nvim [FILE_NAME]

--rmで一回使い捨てのコンテナを立ち上げ、実行ユーザをホスト側の実行ユーザと合わせた上でゲスト側のhomeディレクトリを/rootに設定し、ホスト側のhomeディレクトリをまるまるマウントして--workdirにホスト側のカレントパスを渡すことで編集するファイルを一致させています。
これによってホスト側のファイルをdockerの存在を意識せずに編集できるようになりました。

長いのでvimにaliasを貼っています。

homeディレクトリごとマウントすることでディレクトリ構造とパスを完全一致させた

参考記事だとカレント以下をマウントしていますが、例えば.prettierrcなどがプロジェクトルートにある場合、常にプロジェクトルートから実行しなければ設定が読み込まれずとんでもないことになります。
私は普段コマンドラインで頻繁にディレクトリを移動しながらファイルを開いたり閉じたりするので結構深刻です。
また、nvim内から親ディレクトリを参照できなくなるのも不便です。

いっそのことhomeディレクトリごとマウントしてしまうことで、難しいことを考えずに必要な親ディレクトリを辿れるようにしました。
$HOMEより上はさわれませんがまあそもそもそれらのファイルって大体rootユーザでいじるようなものなので、触れなくて困らないかなと思っています。

実行ユーザがrootのせいでファイルパーミッションがroot:rootになってしまう問題を解決した

一番苦労した点です。

特に何も考えずにそのままdockerで走らせてしまうとdocker側の実行ユーザがrootになるため、編集したファイルはホスト側からはroot:rootになってしまいます。
既存ファイルを編集した場合には問題ありませんが、これはものすごく不便なのでなんとかして解決したく非常に苦労しました。

というのも、dockerイメージのビルド時点ではホスト側のユーザidがいくつなのか関与することができず事前にユーザを用意できません。
これによりdocker run時に実行ユーザを変えてしまうと今度homeディレクトリが存在しないので、事前に構築したnvimの設定が使えません...

dockerイメージ内に確実に存在しているのはrootユーザであり、nvimの設定が記述されているのもrootユーザです。
当初docker内でシステムグローバルにこれらを管理できないか試していましたが、init.vimはともかくプラグイン管理は難しく諦めました。

結果として、nvimの設定構築後に/rootのパーミッションをゆるくしてしまい、dockerコンテナ実行時に実行ユーザを変えた上でhomeディレクトリを/rootに設定してnvimの設定群を自由に触れるようにしてやればよいのではという形で解決しました。
これが先のRUN chmod -R 777 /rootの理由です。

気持ち悪さの残る解決法ですが、まあ運用上は一回ごとにコンテナを捨てるし、ファイルの編集以外に使わないしまあいいか...という妥協をしています。

ちなみにmacの場合はなんか知らんけどゲスト側で作成したファイルのパーミッションがホスト側の実行ユーザに合わせられるのであんま気にする必要がありません。

オーバーヘッド

ほぼ感じません。というか気になってません。
ホストがLinuxなのもあってか、普通にnvimを立ち上げるのとあんま変わらないくらいの速度で起動してくれます。

終わりに

それなりの期間この環境でのnvimを常用していますが、特別ストレスは感じていません。dockerで動かしてることを忘れるくらいです。
インターンなりバイトなりで初めてのマシンを使うときの環境構築も数分で終わるようになりましたし、気分転換に環境をクリーンにすることも気楽にできるようになりました。

気になっている点としては、

  • クリップボードが共有されない
  • rootユーザより実行専用の別ユーザを作ったほうがいいんじゃないか

くらいです。
クリップボードに関してはXのソケットを共有すれば解決できそうな予感がするのでそのうちやろうかと思ってます。
ユーザに関してはまあ...いいかな別に...パーミッション問題は解決したし......

ただいい感じに落ち着いてきた頃に就活が終わってもういろんな会社に行かなくなったのでもうちょっと早く挑戦しときゃよかったなとなりました。