🎻

Symfony5+PostgreSQL+Firebase AuthなアプリをGitHub ActionsでHerokuにデプロイするまで

2022/02/08に公開

意外と躓いたのでブログに残しておきます。

1. Procfile を作成

PHP 向けの Web サーバーおよびランタイム設定のカスタマイズ | Heroku Dev Center #nginx

あたりを参考に heroku/nginx/default.conf を以下の内容で作成して、

location / {
    # try to serve file directly, fallback to rewrite
    try_files $uri @rewriteapp;
}

location @rewriteapp {
    # rewrite all to index.php
    rewrite ^(.*)$ /index.php/$1 last;
}

以下の内容で Procfile を作成します。

web: vendor/bin/heroku-php-nginx -C heroku/nginx/default.conf public/

2. app.json を作成

Heroku側の環境設定はWeb UIなどで行うとしても、インフラの情報をコードベースに残す意味で app.json を作っておきます。

{
  "buildpacks": [
    {
      "url": "heroku/nodejs"
    },
    {
      "url": "heroku/php"
    }
  ],
  "addons": [
    {
      "plan": "heroku-postgresql:hobby-dev",
      "options": {
        "version": "13"
      }
    }
  ],
  "env": {
    "APP_ENV": "dev",
    "APP_SECRET": {
      "generator": "secret"
    },
    "DATABASE_URL": "",
    "FIREBASE_CREDENTIALS": {
      "description": "Firebaseの秘密鍵"
    }
  }
}

3. composer.jsoncompile カスタムコマンドを追加

  • デプロイ時に bin/console doctrine:migrations:migrate を実行する
  • デプロイ時に --no-dev なしで composer install する
  • --no-devcomposer install されたときは @auto-scripts を実行しない(require-dev のクラスたちが ClassNotFoundError になってしまって @auto-scripts の実行が失敗してしまうので)

という3つのことを達成すべく、composer.json を修正します。

詳しくは

[PHP] Herokuへのデプロイでrequire-devの依存もインストールする

過去にこちらの記事で解説していますので、先に目を通してみていただけるとよいかもしれません✋

{
    "scripts": {
        "auto-scripts": {
            "cache:clear": "symfony-cmd",
            "assets:install %PUBLIC_DIR%": "symfony-cmd"
        },
        "auto-commands": [
            "npm install"
        ],
        "post-install-cmd": [
            "if [ $COMPOSER_DEV_MODE -ne 0 ]; then composer auto-scripts; fi",
            "@auto-commands"
        ],
        "post-update-cmd": [
            "if [ $COMPOSER_DEV_MODE -ne 0 ]; then composer auto-scripts; fi",
            "@auto-commands"
        ],
        "db-migrate": "bin/console doctrine:migrations:migrate --no-interaction --no-debug --allow-no-migration",
        "compile": [
            "composer install --prefer-dist --optimize-autoloader --no-interaction",
            "@db-migrate"
        ]
    }
}

こんな感じです。

ちなみに、最初は

        "db-migrate": "bin/console doctrine:migrations:migrate --no-interaction --no-debug --allow-no-migration",

ではなく

        "db-migrate": [
            "bin/console doctrine:database:create --if-not-exists",
            "bin/console doctrine:migrations:migrate --no-interaction --no-debug --allow-no-migration"
        ],

と書いていたんですが、Heroku Postgres だと bin/console doctrine:database:create --if-not-exists を叩いた時点で User does not have CONNECT privilege というエラーになりました。

参考:Why am I seeing "User does not have CONNECT privilege" error with Heroku Postgres on Review Apps? - Heroku Help

webpack-encore を導入している場合は、npm install 後に npm run build が実行されるよう package.json も修正しておきましょう。

  {
    "scripts": {
      "dev-server": "encore dev-server",
      "dev": "encore dev",
      "watch": "encore dev --watch",
      "build": "encore production --progress",
+     "postinstall": "npm run build"
    }
  }

4. Firebaseの秘密鍵を環境変数からファイルに出力するように

kreait/firebase-bundle を使っている場合、Firebaseの秘密鍵はファイルから読み込むことしかできません。

kreait/firebase-php には 秘密鍵のJSON文字列を渡せるAPIがある のですが、kreait/firebase-bundle ではそこは隠蔽されてファイル読み込みしかできなくなっています。

Herokuでは ファイルシステムが永続的ではない ので、環境変数に秘密鍵をセットしておいて、デプロイ時にその内容をファイルに出力するようにする必要があります。

そこで、composer.jsonauto-commands カスタムコマンドに以下のような2行を追記してみましたが、これだと上手く行きませんでした。

  {
      "scripts": {
          "auto-commands": [
              "npm install",
+             "if [ ! -f firebase-credentials-dev.json ]; then echo $FIREBASE_CREDENTIALS > firebase-credentials-dev.json; fi",
+             "if [ ! -f firebase-credentials-prod.json ]; then echo $FIREBASE_CREDENTIALS > firebase-credentials-prod.json; fi"
          ]
      }
  }

原因はよく分かっていませんが、Heroku環境上 && Composerカスタムコマンド経由の場合のみ、echo $FIREBASE_CREDENTIALS\n を改行文字として出力してしまい、正しい秘密鍵の内容ではなく \n という文字列が改行文字に置換された内容がファイルに書き込まれて、Symfonyのコンテナのコンパイルが失敗してしまいました。

echo -E オプションで行けるかと思ったけどHeroku環境の echo コマンドには -E オプションありませんでした。

なので、echo コマンドの代わりに php -r を使って以下のように対処したところ期待どおりに動作しました。

  {
      "scripts": {
          "auto-commands": [
              "npm install",
-             "if [ ! -f firebase-credentials-dev.json ]; then echo $FIREBASE_CREDENTIALS > firebase-credentials-dev.json; fi",
-             "if [ ! -f firebase-credentials-prod.json ]; then echo $FIREBASE_CREDENTIALS > firebase-credentials-prod.json; fi"
+             "if [ ! -f firebase-credentials-dev.json ]; then php -r \"echo getenv('FIREBASE_CREDENTIALS');\" > firebase-credentials-dev.json; fi",
+             "if [ ! -f firebase-credentials-prod.json ]; then php -r \"echo getenv('FIREBASE_CREDENTIALS');\" > firebase-credentials-prod.json; fi"
          ]
      }
  }

5. GitHub ActionsからHerokuにデプロイできるように

ここまでで、コードベースはHerokuにデプロイ可能な状態になりました。

最後に、GitHub Actionsでテストが成功したときのみHerokuへデプロイするようワークフローを書きます。

name: CI

on: push

jobs:
  test:
    # 略

  deploy:
    if: ${{ github.ref == 'refs/heads/main' }} # mainブランチへのpushでのみ実行
    needs: test # testをパスした場合のみ実行
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: akhileshns/heroku-deploy@v3.12.12
        with:
          heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
          heroku_app_name: YOUR_HEROKU_APP_NAME
          heroku_email: ${{ secrets.HEROKU_EMAIL }}

参考:

Deploy to Heroku アクションを使えばGitHub Actionsから簡単にHerokuにデプロイできます。

HerokuのAPIキーとHerokuアカウントのメールアドレスが必要なので、

https://dashboard.heroku.com/account/applications

Create Authorization でAPIキーを作成して、

https://github.com/{owner}/{repo}/settings/secrets/actions

New repository secretHEROKU_API_KEY として登録します。同様に HEROKU_EMAIL としてメールアドレスも登録します。

これで、mainブランチが更新されてテストがパスしたら自動でHerokuにデプロイされるようになりました👍

GitHubで編集を提案

Discussion