Dockerを使ってcomposerをHHVMで動かす


TL;DR

  • brewのcomposerはcomposer.pharではないのでhhvm /usr/local/bin/composerしてもPHPで動いてしまう
  • コンテナ上にHHVMとcomposerを用意して解決したので、Dockerfileとその使い方を公開します

動作環境

  • macOS High Sierra(version 10.13.3)
  • Docker Community Edition(version 18.03.0-ce-rc1-mac54)

経緯

PHPerKaigi 2018Hackの話を聞いて触発されてHackを触ってみたものの、hhvm/hhvm-autoloadをインストールしようとすると、HHVMで実行してるのにもかかわらず「HHVMで動かせ」って怒られてしまいました。

$ which composer
/usr/local/bin/composer

$ hhvm /usr/local/bin/composer require hhvm/hhvm-autoload
Using version ^1.6 for hhvm/hhvm-autoload
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - hhvm/hhvm-autoload v1.6.2 requires hhvm ^3.23 -> you are running this with PHP and not HHVM.
    - hhvm/hhvm-autoload v1.6.1 requires hhvm ^3.23 -> you are running this with PHP and not HHVM.
    - hhvm/hhvm-autoload v1.6.0 requires hhvm ^3.23 -> you are running this with PHP and not HHVM.
    - Installation request for hhvm/hhvm-autoload ^1.6 -> satisfiable by hhvm/hhvm-autoload[v1.6.0, v1.6.1, v1.6.2].


Installation failed, reverting ./composer.json to its original content.

原因

このcomposerはbrew install composerでインストールしたものなので、/usr/local/bin/composercomposer.pharとは別物のようです。

$ cat /usr/local/bin/composer
#!/usr/bin/env php
<?php
array_shift($argv);
$arg_string = implode(' ', array_map('escapeshellarg', $argv));
$arg_prefix = preg_match('/--(no-)?ansi/', $arg_string) ? '' : '--ansi ';
$arg_string = $arg_prefix . $arg_string;
passthru("/usr/bin/env php -d allow_url_fopen=On -d detect_unicode=Off /usr/local/Cellar/composer/1.6.3/libexec/composer.phar $arg_string", $return_var);
exit($return_var);

Cellarの中にあるcomposer.pharをHHVMで実行して解決することもできそうですが、なるべく触りたく無い(個人の感想)のでDocker環境を用意することにしました。

できたもの

Dockerfile
FROM hhvm/hhvm

## get composer from composer official image
COPY --from=composer /usr/bin/composer /usr/local/bin/composer

## create non-root user
ARG PUID=1000
ARG PGID=1000

RUN groupadd -g ${PGID} docker && \
  useradd -u ${PUID} -g docker -m docker

## use non-root user
USER docker

# --------------------------------------------------
# additional composer settings
# --------------------------------------------------

ARG USE_PACKAGIST_JP=false
RUN if [ ${USE_PACKAGIST_JP} = true ]; then \
  hhvm /usr/local/bin/composer config -g repos.packagist composer https://packagist.jp \
  ;fi

ARG INSTALL_PRESTISSIMO=false
RUN if [ ${INSTALL_PRESTISSIMO} = true ]; then \
  hhvm /usr/local/bin/composer global require hirak/prestissimo \
  ;fi

# --------------------------------------------------
# final touches
# --------------------------------------------------

WORKDIR /app

## set -d option to control "SlowTimer" warning
ENTRYPOINT [ "hhvm", "-d", "hhvm.http.slow_query_threshold=30000", "/usr/local/bin/composer" ]

CMD [ "--help" ]

ライセンスは放棄しますので、ご自由にお使いください。

解説

ベースイメージ

FROM hhvm/hhvm

ベースイメージはhhvm/hhvm(ubuntu 14.04ベース)です。450MBほどあります。multi-stage buildsを利用すればalpineやbusyboxをベースイメージとして作れるのでは?と思って試行錯誤したのですがうまくいかず、時間の無駄だと判断してやめました。

composerを持ってくる

COPY --from=composer /usr/bin/composer /usr/local/bin/composer

composerはオフィシャルのイメージからもらってきています。/usr/bin/composerとpathを決め打ちしているのが気になりますが見なかったことにします。multi-stage buildsは便利ですね。

参考

ルート権限のないユーザを用意する

ARG PUID=1000
ARG PGID=1000

RUN groupadd -g ${PGID} docker && useradd -u ${PUID} -g docker -m docker

USER docker

composerはroot権限を持ったユーザで実行すると怒られるので、適当なユーザを用意しています。

composerの設定をする

ARG USE_PACKAGIST_JP=false
RUN if [ ${USE_PACKAGIST_JP} = true ]; then \
  hhvm /usr/local/bin/composer config -g repos.packagist composer https://packagist.jp \
  ;fi

ARG INSTALL_PRESTISSIMO=false
RUN if [ ${INSTALL_PRESTISSIMO} = true ]; then \
  hhvm /usr/local/bin/composer global require hirak/prestissimo \
  ;fi

このあたりは趣味です。なのでデフォルトではインストールしたりしないようにしています。

インストールしたい方は、イメージをビルドするときに--build-argオプションで変えてください。(後述)

参考

残り

WORKDIR /app

ENTRYPOINT [ "hhvm", "-d", "hhvm.http.slow_query_threshold=30000", "/usr/local/bin/composer" ]

CMD [ "--help" ]

コンテナを起動するときにワーキングディレクトリにcomposer.jsonのあるディレクトリをマウントすることで、コンテナの中で実行したcomposerの成果物がホスト側に反映されます。

エントリーポイントでは、composerをHHVMで実行すると発生する「SlowTimer」を抑制するためのオプションをつけています。

参考

使い方

イメージをビルドする

とりあえずビルドするならこのコマンドを打ってください。

docker build -t hh-composer /path/to/Dockerfile

オプション

--build-argオプションで変えられます。

  • USE_PACKAGIST_JP=true|false - packagist.jpを参照するようにするかどうか。デフォルトはfalse
  • INSTALL_PRESTISSIMO=true|false - composerを早くするプラグイン(hirak/prestissimo)をインストールするかどうか。デフォルトはfalse

設定例

packagist.jpを使い、hirak/prestissimoをインストールする場合。

docker build --build-arg USE_PACKAGIST_JP=true --build-arg INSTALL_PRESTISSIMO=true -t hh-composer /path/to/Dockerfile

変更する数だけ--build-argオプションを書かないといけないのは少し面倒くさいですね。

コンテナを起動してcomposerを実行する

// 長いオプションだとこう
docker run --rm --tty --interactive --volume $(pwd):/app hh-composer

// 短いオプションだとこう
docker run --rm -it -v $(pwd):/app hh-composer

hh-composerに何も引数を付けないと、--helpが呼ばれるようになってます。

それでは、当初の目的であったhhvm/hhvm-autoloadをインストールしてみます。

$ docker run --rm -it -v $(pwd):/app hh-composer require hhvm/hhvm-autoload
Using version ^1.6 for hhvm/hhvm-autoload
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
  - Installing fredemmott/hack-error-suppressor (v1.0.1): Downloading (100%)
  - Installing hhvm/hhvm-autoload (v1.6.2): Downloading (100%)
Writing lock file
Generating autoload files

いい感じですね

使いやすくする

いちいち長いコマンドを打つのはめんどくさいので、shellの設定ファイル(.bashrc .zshrcなど)に関数を追加します。

hh-composer () {
  tty=
  tty -s && tty=--tty
  docker run \
    $tty \
    --interactive \
    --rm \
    --volume $(pwd):/app \
    hh-composer "$@"
}

これでhh-composerとして実行することができるようになります

$ hh-composer --version
Composer version 1.6.3 2018-01-31 16:28:17

参考

注意点

  • composerとHHVMはコンテナ上で動いていて、コマンド実行後すぐに破棄されてしまうため、composer global installしたい場合はDockerfileをいじるしかありません
  • composerとHHVMのバージョンがコンテナ側とホスト側で一致してないことでエラーが起きる可能性があります
  • 一部のpackageはインストール時にscriptを実行しますが、それが反映されるのはコンテナ側になります。それによって問題が起きる可能性があります

最後に

誤字脱字、内容の間違いなどは編集リクエスト、コメント、Twitter(@shuymn)など何らかの方法で教えていただければと思います。