はじめに

Symfony2 で作ったアプリを初めて Heroku にデプロイしたんですが、意外と Web に情報が少なくてちょいちょい困ったので、実際に行った手順をまとめておきます。

Symfony2 のアプリはローカルで動いてて、あとは適切にデプロイしたいだけ、という前提のお話です。(Symfony のバージョンは 2.6 です)

BEAR.Sunday アプリのお話も こちら にありますので、よろしければご覧ください。

Heroku の環境準備

まずは Heroku 自体を使える状態になりましょう。

1. アカウントを作成

何はともあれ Heroku の アカウントを作成 します。

2. heroku-toolbelt をインストール

heroku コマンドを使えるようにするために、heroku-toolbelt というツールをインストールします。

Web からダウンロード してインストールしてもよいですし、brew-cask を使っている場合は以下のようにコマンドでもインストールできます。

$ brew cask install heroku-toolbelt

Heroku 上にアプリを作成

次に、Heroku 上にアプリを作成します。

Web UI から作成 してもよいですし、コマンドでも作成できます。

$ cd {Symfony2 プロジェクトのルートディレクトリ}   # heroku コマンドは基本的にここで使用します
$ heroku login
Enter your Heroku credentials.
Email: {メールアドレス}
Password (typing will be hidden): {パスワード}
Authentication successful.
$ heroku create {付けたいアプリ名}   # アプリの公開 URL が https://{付けたいアプリ名}.herokuapp.com になります

heroku create でアプリを作成した場合は、ローカルの Git リポジトリに heroku というリモートリポジトリが自動で追加されます。 これが追加されている状態だと、heroku コマンド実行時に --app {アプリ名} の指定を省略できます。

Web UI からアプリを作成した場合は、

$ heroku git:remote -a {アプリ名}

を実行してリモートリポジトリを追加しておきましょう。

Heroku 上でビルド・実行できるように小細工

あとはソースコード一式をリモートリポジトリ heroku に push すればデプロイできるのですが、Symfony2 プロジェクトを Heroku 上で正常に動作させるためにはいくつか小細工が必要です。

順番に片付けていきましょう。

1. SYMFONY_ENV 環境変数を prod にする

Heroku に PHP アプリをデプロイすると、自動的に composer install --no-dev が実行されるため、require-dev で依存しているライブラリはインストールされません。(参考

そのため、SYMFONY_ENV が設定されていない状態(つまりデフォルトで dev となる状態)でビルドしようとすると、以下のようなエラーで失敗します。

PHP Fatal error:  Class 'Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle' not found in /tmp/build_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/app/AppKernel.php on line 33
Script Sensio\Bundle\DistributionBundle\Composer\ScriptHandler::clearCache handling the post-install-cmd event terminated with an exception

  [RuntimeException]
  An error occurred when executing the "'cache:clear --no-warmup'" command.

これを回避するには、Heroku の環境で SYMFONY_ENV 環境変数の値を prod に設定しておけばよいです。

Web UI (https://dashboard.heroku.com/apps/{アプリ名}/settings) から設定してもよいですし、コマンドでも設定できます。

$ heroku config:set SYMFONY_ENV=prod

2. プロジェクトローカルな node_modules を使うようにする

LESS を使っている場合は、Heroku 上にも less コマンドがインストールされている必要があります。

もしローカル環境で global な node_modules を使って動かしている場合は、Heroku 上でも使えるようにプロジェクトローカルな node_modules を使う設定にしておく必要があります。

config.yml の修正

 assetic:
     filters:
         less:
             node_paths:
+                - "%kernel.root_dir%/../node_modules"
                 - "/usr/lib/node_modules"

packages.json の作成

プロジェクトローカルな node_modulesless コマンドをインストールできるように、packages.json を作成します。 (packages.json は、composer.json の npm 版みたいなものです)

プロジェクトのルートディレクトリに packages.json を作成して、以下のような内容にします。

{
    "repository": {
        "private": true
    },
    "dependencies": {
        "less": "~1.7"
    }
}

.gitignore の修正

この状態で

$ npm install

を実行すると、node_mondules 配下にパッケージがインストールされます。

この node_modules は Git で管理したくないので、.gitignore に追加しておきましょう。

3. PHP 用と Node.js 用の両方の Heroku 環境を使えるようにする

Heroku では基本的に、プロジェクト内に特定のファイルがあるかどうかによって自動で環境が決定されます。(PHP の場合は index.php があるかどうかで判別されるようです)

この環境のことを Buildpack と呼びます。

Symfony2 のプロジェクトを普通にデプロイすると、index.php を見つけて PHP 用の Buildpack が自動で適用されます。

しかし、この Buildpack には node の環境が入っていないため、npm install が実行できません。 これでは LESS のコンパイルができないので、PHP 用の Buildpack と Node.js 用の Buildpack(npm install が実行される)を両方適用させる必要があります。

具体的には、以下の 2 つを実施すればよいです。

  • heroku-buildpack-multi を使うよう設定
  • プロジェクトルートに .buildpacks というファイルを作成し、使いたい Buildpack を記載

heroku-buildpack-multi を使う

以下のコマンドで heroku-buildpack-multi を使うように設定することができます。

$ heroku buildpack:set https://github.com/heroku/heroku-buildpack-multi

このコマンドが実際に行うのは、BUILDPACK_URL 環境変数の値に Buildpack のリポジトリの URL を設定するというだけなので、Web UI から行っても問題ありません。

.buildpacks を作成

プロジェクトルートに、以下の内容で .buildpacks ファイルを作成します。

https://github.com/heroku/heroku-buildpack-nodejs
https://github.com/heroku/heroku-buildpack-php

順番を逆にすると、composer installnpm install の順で実行されるようになってしまい、LESS のコンパイル時に /usr/bin/node: not found というエラーになってしまうので注意が必要です。

4. デプロイ後に assetic:dump させる

多くの場合、prod 環境ではデプロイ後に assetic:dump コマンドを実行する必要があると思います。

Heroku ではこのようなデプロイ時に実行させたいスクリプトを composer.json で指定します

{
    "scripts": {
        "compile": [
            "php app/console assetic:dump"
        ]
    }
}

普通に post-install-cmd に追加してもいいのですが、それだと dev 環境でインストールしたときにも走ってしまってローカルでの開発の邪魔になります。 compile に書いておけば Heroku でしか実行されないので安心です。

起動スクリプトを用意

さて、小細工も済んだので、あとはプロジェクトルートに Heroku 用の起動スクリプトを設置すれば準備完了です。

プロジェクトルートに Procfile というファイルを設置して、以下のような内容にします。

web: bin/heroku-php-apache2 web/

もし composer.jsonbin-dir の設定が bin でない場合は、適宜読み替えてください。

例えば bin-dir を特に設定していない場合は、デフォルトで vendor/bin になるので

web: vendor/bin/heroku-php-apache2 web/

とすればよいです。

デプロイしてみる

いよいよデプロイしてみましょう。

$ git push heroku master

特にエラーなく終了したら、

$ heroku open

とすればブラウザでアプリのページが開きます。ちゃんと動いていたらバンザイしましょう。

おまけ:データベースを設定

データベースを使う方法についても最後に説明しておきます。今回は MySQL を使うケースを例に取ります。

アドオンを追加

Heroku では、アドオン を追加することで機能を拡張することができます。

MySQL のアドオンは

  • https://addons.heroku.com/cleardb
  • https://addons.heroku.com/jawsdb

の 2 つがあるようですが、今回は JawsDB の方を使ってみようと思います。

アドオンの追加方法は簡単で、このページ に記載があるとおり

$ heroku addons:add jawsdb

とするだけです。

完了すると

Please allow 10-15 minutes for your new database to be provisioned. Your config var will be updated automatically when it is finished.

というメッセージが出るので、言われたとおりしばらく待ちましょう。

無料のアドオンであっても クレジットカード情報を登録 しておかないと追加できないみたいなので要注意です。

parameters.yml にデータベース情報を反映させる

データベースが出来上がるのを待つ間に、Symfony2 からデータベースに接続するための準備を進めましょう。

デプロイ時に、parameters.yml の内容が環境変数から設定されるようにします。 詳しくは [Symfony2] 環境毎に app/config/parameters.yml を自動的生成 を参照してください。

まずは composer.json に以下を追記します。

{
    "extra": {
        "incenteev-parameters": {
            "file": "app/config/parameters.yml",
            "env-map": {
                "database_host": "APP_DATABASE_HOST",
                "database_port": "APP_DATABASE_PORT",
                "database_name": "APP_DATABASE_NAME",
                "database_user": "APP_DATABASE_USER",
                "database_password": "APP_DATABASE_PASSWORD"
            }
        }
    }
}

あとは

  • APP_DATABASE_HOST
  • APP_DATABASE_PORT
  • APP_DATABASE_NAME
  • APP_DATABASE_USER
  • APP_DATABASE_PASSWORD

これらの環境変数に実際の値をセットすれば準備は完了です。

以下のコマンドでデータベース情報を確認してください。

$ heroku config | grep JAWSDB
JAWSDB_URL: mysql://uuuuuuuuuuuuuuuu:pppppppppppppppp@hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh.amazonaws.com:3306/heroku_app_db

もし出力結果が

JAWSDB_URL: mysql://This:Text@WillAutomatically.UpdateOnce.DatabaseIsReady:1234/heroku_app_db

こんな感じだったら、まだデータベースのプロビジョニング中なので、もう少し待ちましょう。

この URL は、

mysql://{ユーザ名}:{パスワード}@{ホスト名}:{ポート番号}/{データベース名}

という構造になっています。それぞれの値を抜き出して、以下のように環境変数を設定しましょう。

$ heroku config:set APP_DATABASE_HOST=hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh.amazonaws.com
$ heroku config:set APP_DATABASE_PORT=3306
$ heroku config:set APP_DATABASE_NAME=heroku_app_db
$ heroku config:set APP_DATABASE_USER=uuuuuuuuuuuuuuuu
$ heroku config:set APP_DATABASE_PASSWORD=pppppppppppppppp

デプロイ後にデータベースのスキーマを更新させる

このままだとデータベースはあるけどテーブルは全くない状態なので、デプロイ後に一度だけ

$ heroku run bash
> php app/console doctrine:schema:create

ってやってもいいんですが、スキーマの変更が自動で反映されるようにしたければ、composer.json に以下を追記してからデプロイしてもよいです。

 {
     "scripts": {
         "compile": [
             "php app/console assetic:dump"
+            "php app/console doctrine:schema:update --force"
         ]
     }
 }

ちゃんとマイグレーションしたいとかプロジェクトによって色々あると思うので、今回はたまたまこうしました、というお話です。

おわりに

思ったより長文になってしまいましたが、Symfony2 のアプリを Heroku にデプロイするための手順が一通りご紹介できたかなと思います。

ちなみに今回実際に作ったアプリは これ です。社内でちょっとした多数決とかをとりたいときに使える超簡易なアンケートツールみたいなのが欲しかったので作りました。(まだ未完成ですが、とりあえず動くだけ動きます)

ローカルで動いている状態から Heroku に対応させるために行った変更は このコミット にまとまっているので、よろしければ参考にしてみてください。