Heroku Button で laravel アプリケーションを一発デプロイできるようにした話

# はじめに

本稿は Heroku Advent Calendar 2017 の19日目です。
「Heroku Buttonでlaravelアプリケーションを一発デプロイできるようにした話」と称して、
DBアドオンの取り扱いまで含めてlaravelアプリケーションをボタン一発でデプロイできるようにした手順を紹介します。

# 前提

- git
- heroku Toolbelt
- laravel
- composer
- npm (or yarn)

上記は既にインストール済み、基本的な使い方がわかっているものとして進めます。
また、アプリケーションはGitHubなどのGitホスティングサービスにてリポジトリを公開しているものとして解説をしていきます。
今回は`git push heroku`を使ってherokuにデプロイする方法ではないのであしからず。

# 手順

2. Procfileの設定
3. app.jsonの設定
4. SeederやAPP_KEYの設定
5. Heroku Buttonの設置

## 1. Procfileの設定

@プロジェクトルート

```sh
$ echo web: vendor/bin/heroku-php-apache2 public > Procfile
```

herokuはProcfileの内容を元にDynoと呼ばれるプロセスのようなものを立ち上げます。
なのでherokuが提供するApacheでPHPアプリケーションをホストするためのコマンドを指定してあげましょう。
また、laravelアプリケーションはpublicディレクトリをルートディレクトリとして動作するので、引数としてpublicを渡します。

## 2. app.jsonの設定

Heroku Buttonはプロジェクトルートのapp.jsonの内容に従ってアプリケーション作成ページを生成します。

```sh
$ touch app.json
```

```json:app.json
{
  "name": "(アプリケーション名)",
  "description": "(アプリケーション説明)",
  "keywords": [
    "laravelなど適当なキーワード群"
  ],
  "repository": "(リポジトリURL)",
  "logo": "(ロゴ画像URL)",
  "env": {
    "DB_CONNECTION": {
      "description": "DBドライバ(laravelのDBをPostgreSQLに指定)",
      "value": "pgsql"
    }
  },
  "addons": [
    "heroku-postgresql"
  ],
  "buildpacks": [
    {
      "url": "https://github.com/heroku/heroku-buildpack-php"
    },
    {
      "url": "heroku/nodejs"
    }
  ],
  "scripts": {
    "postdeploy": "php artisan migrate:fresh --force && php artisan db:seed --force"
  }
}
```

`addons`: `heroku-postgresql`を指定。MySQLのHerokuアドオンとしてClearDBがありますが、これはidのインクリメントが10単位(1, 11, 21, ...など)なのでPostgreSQLを利用します。
EloquentモデルはDBを抽象化するため、Eloquentモデルを活用してアプリケーションを作成しているなら問題なくローカルでMySQLで実装の動作確認をしていてもスムーズに移行が可能でしょう。

`buildpacks`: PHP用とNode.js用(フロントエンドのビルドのために必要)の両方を指定しておきます。
npmとyarnはそれぞれのlockファイルの有無に応じて自動判別されるため、これら両方をプロジェクトルートに配置するのはやめましょう。

`scripts`: `postdeploy`(初期デプロイ時の一度しか走らない)を利用して、MigrationとDatabaseSeederを走らせます。
herokuはproduction環境として扱われるため、`--force`オプションを指定しないとダイアログが発生しMigrationが進まずタイムアウトでデプロイ失敗します。

もちろん対話型のapp.json生成を利用した後これらの設定を行ってもOKです。

## 3. 環境変数の設定

DB設定は後述、APP_KEYはcomposer scriptsを通じて生成するので、それら以外の設定を記述した`.env`ファイルを`.env.heroku`などの名前であらかじめ保存しておきましょう。

`composer.json`の`scripts`に以下を追記します。

```json:composer.json
{
    ...
    "scripts": {
        ...
        "post-install-cmd": [
            "@php artisan optimize",
            "chmod -R 777 storage",
            "@php artisan passport:keys"
        ],
        "compile": [
            "@php -r \"file_exists('.env') || copy('.env.heroku', '.env');\"",
            "@php artisan key:generate"
        ]
    },
    ...
}
```

`post-install-cmd`: storageの権限変更とpassportのkey生成。必要があれば。

`compile`: `.env`が存在しない場合、`.env.heroku`を`.env`にコピーし、APP_KEYを生成します。
`compile`コマンドは`post-install-cmd`コマンドに含めたくない処理がある場合に備えて、Herokuが実行するカスタムスクリプトです。
これを設定しておくことでHerokuでのビルド時に特別な処理を行うことが可能になります。
参考: https://devcenter.heroku.com/articles/php-support#custom-compile-step

### DB設定

DBのホスト設定などDBの接続先情報は`heroku-postgresql`アドオンを導入した際に接続用URLが環境変数として既に発行されているので、これをパースして読み込むようにします。

```php:config/database.php
<?php

$host = '127.0.0.1';
$db = 'forge';
$user = 'forge';
$password = '';

if (env('DATABASE_URL', false) && env('DB_CONNECTION', 'mysql') === 'pgsql') {
    // heroku-postgresql 用設定。
    $match = [];
    preg_match('/postgres:\/\/(.*?):(.*?)@(.*?):(.*?)\/(.*)/', env('DATABASE_URL'), $match);

    $host = $match[3];
    $db = $match[5];
    $user = $match[1];
    $password = $match[2];
}
...
    'connections' => [
        ...
        'pgsql' => [
            'driver' => 'pgsql',
            'host' => env('DB_HOST', $host),
            'port' => env('DB_PORT', '5432'),
            'database' => env('DB_DATABASE', $db),
            'username' => env('DB_USERNAME', $user),
            'password' => env('DB_PASSWORD', $password),
            'charset' => 'utf8',
            'prefix' => '',
            'schema' => 'public',
            'sslmode' => 'prefer',
        ],
        ...
    ],
...
```

`heroku-postgresql`は`DATABASE_URL`環境変数に`postgres://(ユーザー名):(パスワード)@(ホスト名):(ポート番号)/(DB名)`で接続情報を発行するので、それを正規表現でパースしているだけです。

`.env`ファイルなどで特定の接続情報が与えられている場合はそちらを利用します。

## 4. Heroku Buttonの設置

リポジトリの`README.md`の好きな位置に、以下を記述します。

```md:README.md
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
```

あとはGitHubなどに現在の状態をコミット・プッシュして`README.md`に「Deploy to Heroku」ボタンが表示されていれば完了です!

ボタンをポチッと押してみて動作を確認してみましょう!

# まとめ

これで自分の作ったlaravelアプリケーションをワンボタンでHerokuへデプロイすることが可能になります。
アプリケーションの利用への敷居が下がるので、より多くの人に自分のアプリケーションを楽しんでもらうことができるかもしれません。

以上、Heroku Advent Calendar 2017 19日目、「Heroku　Buttonでlaravelアプリケーションを一発デプロイできるようにした話」でした。