Clojure + Emacsな開発環境を作った on Docker

前回記事「Common Lisp開発環境 on Dockerの現状 - eshamster’s diary」の冒頭で少し書いていたように、Clojure開発用のDocker環境を作ってみました。そろそろいい加減にClojureScriptをいじってみようかというのが主な動機です。

Docker上に開発環境を起こすのはCommon Lispに続いてようやく2言語目なので、言語ごとの開発環境を量産するための知見(主にEmacsの設定をどう管理するか)を得るという副目標もあります。

先に完成品のリンクを貼っておきます。


目次

概要

次のような構成を目指します。

  1. Alpine Linux:軽量なLinux
  2. LeiningenClojureプロジェクトの色々な管理をするソフト(Node.jsで言うnpmのような位置付け)
  3. Emacs

1, 2に関しては公式のDockerイメージが提供されているので、DockerfileでFROM clojure:lein-2.8.1-alpineのようにするだけです。

3のEmacsの設定については下記を参考…というよりClojure部分についてはほぼそのままです。

qiita.com

そんなわけなので、正直余り書くこともなかったりします…。

Dockerfile

Dockerfileの記述ですが、上記の通りClojure (Leiningen) の設定については公式リポジトリからFROMするだけですし、Emacsについてもapkの標準リポジトリからとってくるだけで新しいもの(現在はバージョン25.3.1でした)がとれますし、特筆すべき点がないです…。

FROM clojure:lein-2.8.1-alpine

RUN apk update --no-cache
RUN apk add --no-cache emacs git

# --- install wget with certificate --- #
RUN apk add --no-cache ca-certificates wget openssh && \
    update-ca-certificates

# --- user settings --- #
ARG emacs_home=/root/.emacs.d
ARG site_lisp=${emacs_home}/site-lisp
ARG dev_dir=/root/work

RUN mkdir ${emacs_home} && \
    mkdir ${site_lisp} && \
    mkdir ${dev_dir}

# --- run emacs for installing packages --- #
COPY init.el ${emacs_home}
RUN emacs --batch --load ${emacs_home}/init.el

# --- miscs --- #
WORKDIR /root

Emacsの設定

設定自体について

前述のようにClojure部分の設定については、ほぼ「新: Emacs を使うモダンな Clojure 開発環境 - Qiita」のままです。最低限使いそうなところだけピックアップして言った感じなので、差異や不足のある部分には大して意味はありません。意図的に変えたのは下記程度です(細かい話ですが…)。

  • company-modeauto-completeの代替)のキーバインド
    • 個人的にはM-p, M-nでの候補選択に慣れているので、C-p, C-nへの割り当てはしていません
    • 個人的にはC-iはデフォルトの用途で頻繁に利用するので、候補の強制呼び出しのキーバインドは代わりにC-c C-iとしました
  • RainbowDelimiters
    • 記事中にも「そこまでの効果はない」とありますが、実際昔使ってみたときに嬉しさを感じられなかったので外しました

全体は少し長いので現時点の設定へのリンクだけ貼っておきます。88行目の;; ----- Clojure ----- ;;というコメント以降("auto settings"以降を除く)がClojure関連の設定になります。

https://github.com/eshamster/docker-clj-devel/blob/a3addf8c5dd58d1f2b6fbbd08a75b54aad4a9071/init.el

言語別環境の量産に向けた簡単な考察

さて、Common Lisp用環境を作るときは余り意識していませんでしたが、今回init.elの中で、共通設定*1Clojureの設定を分離しています(前述のように88行目前後が分かれ目)。もう少し踏み込んで、言語別のDocker環境(用のEmacs設定)を管理するためには下記のようにするのが良さそうだというのが今の考えです(実際やってみると変わりそうですが)。

  • Emacs設定ファイル側
    • 共通設定と言語別設定を別ファイルに分ける
    • これらのロードにはinit-loaderるびきちさんの紹介記事リンク)を利用する
      • init.el自体を触ることなく、特定の場所に(特定の形式で)追加の設定ファイルを置くだけで読み込み対象を変えられる、というのはDockerfileの記述と相性が良さそうです
    • これらはDockerfile用リポジトリとは独立したリポジトリ上で管理する
      • 複数言語を同時に利用するような環境が欲しくなった場合のことを考えると、共通設定だけでなく言語別設定もまとめて管理した方が良さそうです
  • Dockerfile周辺側
    • 上記Emacs設定ファイルリポジトリをsubtreeないしsubmoduleとして取り込む
    • Dockerファイル上では必要なものを~/.emacs.d/inits/配下にCOPYコマンドで持っていく

実際に使うとき

実際に開発環境として利用する際にはもう少しローカルな設定が必要になりますが、これこそ以前書いたCommon Lisp用環境記事の同名の章と変わらないので、ポイントのコピペとファイルの貼り付けだけで終わりにします*2

  • ポイント
    • gitの設定やSSH鍵のコピーなどパーソナルな設定をするためのDockerfileを作る
    • ポートやボリュームの設定を記述した設定ファイルを作る(setenv
    • 上記を利用して起動するためのスクリプトを作る(run.sh
      • ./run.shでコンテナをビルド・起動した後は、コンテナ内の/root/workフォルダで作業します

Dockerfile: ※横に利用するSSH鍵を置いておくこと

FROM eshamster/clj-devel:latest

# --- git settings --- #
RUN git config --global user.name "eshamster" && \
    git config --global user.email "hamgoostar@gmail.com"

# --- ssh settings --- #
ARG user=root

ARG SSH_HOME=/root/.ssh
RUN mkdir ${SSH_HOME} && \
    chmod 700 ${SSH_HOME}

USER root
COPY id_rsa ${SSH_HOME}
COPY id_rsa.pub ${SSH_HOME}
RUN chown ${user}:${user} ${SSH_HOME}/* && \
    chmod 600 ${SSH_HOME}/*

# --- --- #
USER ${user}
RUN echo "export LANG=ja_JP.UTF-8" >> ${HOME}/.bashrc

RUN apk add --no-cache openssl-dev

setenv

export HOST_PORT=17381
export GUEST_PORT=18616
export RUN_NAME=clj
export VOLUME=${HOME}/work/clojure

run.sh

#!/bin/bash

set -eu

. "${1:-$(dirname ${0})/setenv}"

docker rmi $(docker images | awk '/^<none>/ { print $3 }') || echo "ignore rmi error"
docker rm `docker ps -a -q` || echo "ignore rm error"

name="clj_web_devel"

docker build -t ${name} .
docker run --name=${RUN_NAME} -p ${HOST_PORT}:${GUEST_PORT} \
  -e "OPEN_PORT=${GUEST_PORT}" \
  -e "HOST_PORT=${HOST_PORT}" \
  -v ${VOLUME}:/root/work \
  -it ${name} /bin/sh

*1:Common Lisp環境のものと実質は同じですが、use-packageを使ってリファクタリングしました

*2:これはこれでリポジトリ起こして管理した方が良いのだろうとは思っています。ただ、今回のように「(DBなしの)Web開発用の最低限の設定」程度であれば共通化できるのですが、例えば、PostgreSQLを使う環境用、Redisを使う環境用…と個別のアプリ向け設定が必要になってきたときにどう管理すべきか考えられてないので足踏みしてます