この記事は「Firebase Advent Calendar 2017」7日目の記事となります。

Firebaseを使ったプロダクトの知見はちらほら見るようになりましたが、デプロイ周りの知見はまだほとんど見ません。ですので私が実際にやっていることをまとめてみました。参考になれば幸いです。

前提とする必須条件

  • 開発、ステージング、本番などにデプロイ先を切り替えられる
  • 秘匿情報はリポジトリに組み込まない
  • CIでデプロイ可能にする

まずはこれが達成できていることを前提にします。

Firebase CLI

まず本記事はFirebase CLIの利用を前提としています。そしてFirebase CLIに対する解説はほぼしないため、こちらについては公式のドキュメントを参照してください。

Firebase CLI リファレンス  |  Firebase

デプロイ先を切替可能とする

たとえばHostingやDatabase関連は、ひとつのプロジェクトにつき、ひとつしか用意されていません。そのため本番環境とステージング環境など複数の環境を用意したい場合は、それぞれのFirebaseプロジェクトを新規作成する必要があります。
これはGoogle公式のドキュメントでも、そんな風に指示しています。

プロジェクトへの追加は firebase use --add を使う方法もありますが、.firebaserc を直接編集した方が手っ取り早いと思います。

.firebaserc
{
  "projects": {
    "release": "project-id",
    "develop": "develop-project-id"
  }
}

firebase initでデフォルト連打してプロジェクトを作成すると default 枠が作られているので、これを意図的に削除しておきます。デプロイ先を明示的に指定することで、デプロイ先間違えという事故を防ぐためです。

$ firebase use develop
$ firebase deploy

CI用に認証トークンを用意する

通常、Firebase CLIを使用する場合、firebase loginで必要なGoogleアカウントへログインする必要があります。しかしそれだとデプロイするたびにログインが必要となりますし、なにより一度ブラウザに遷移するためCIでは利用できません。

という問題を回避するために、 login:ci というコマンドがあります。実行すると login 同様にブラウザで認証処理が呼び出されます。この認証が完了するとトークンを返却します。

$ firebase login:ci

Visit this URL on any device to log in:
https://accounts.google.com/o/oauth2/auth?client_id=......

Waiting for authentication...

✔  Success! Use this token to login on a CI server:

1/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Example: firebase deploy --token "$FIREBASE_TOKEN

# 使用例
$ firebase use develop --token 1/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
$ firebase deploy --token 1/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# 認証トークンを無効化する
$ firebase logout --token 1/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

✔  Logged out token "1/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

ここで取得した認証トークンを使うことで、ログインしていない環境からでもFirebase CLIを使うことができます。

$ firebase use develop --token 1/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
$ firebase deploy --token 1/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

認証トークンは「Firebaseのプロジェクトに紐づくトークン」ではない

気を付てほしいことがあります。ここで発行した認証トークンは、Googleアカウントに紐付いたトークンです。認証したGoogleアカウントが他のFirebaseプロジェクトにもアクセス可能な場合、そのプロジェクトにも利用可能です。

たとえば開発環境(ステージング)の認証トークンでは本番環境にデプロイさせたくない、というケースがあるとします。その場合、開発環境のFirebaseプロジェクトだけアクセスできるGoogleアカウントからトークンを発行しないといけません。また、該当するGoogleアカウントが削除された場合、当然発行された認証トークンは使えなくなります。

これまで生成した認証トークンって確認できる?

わかりません…。少なくとも、Firebase周りの管理画面では確認できる場所がありませんでした。私も知りたい。

Dockerでデプロイ環境を作る

ということで、CLIだけでデプロイできる準備が整ったのでここからが本番です。私の個人的な趣味と、Circle CI 2.0を利用してデプロイできるようにしたかったので、Dockerでデプロイ環境を構築します。

また、なるべくimageを軽くしたかったので、alpineイメージを利用しています。

deploy.dockerfile
FROM node:6.11-alpine

RUN apk update
RUN apk add git
RUN npm install -g firebase-tools

RUN mkdir app
WORKDIR app

ARG FIREBASE_PROJECT="develop"
ARG FIREBASE_TOKEN="x/xxxxxxxxxxxx"

CMD cd functions && npm install && cd ../ \
    && firebase use ${FIREBASE_PROJECT} --token ${FIREBASE_TOKEN} \
    && firebase deploy --token ${FIREBASE_TOKEN}
deploy-develop-compose.yml
version: '2'
services:
  deploy:
    environment:
      - FIREBASE_PROJECT=develop
      - FIREBASE_TOKEN
    build:
      context: ./
      dockerfile: deploy.dockerfile
    volumes:
      - ./:/app

これで、 docker-compose -f ./deploy-develop-compose.yml up --build とすれば、Docker上でデプロイされるようになりました。
ステージング環境やリリース環境へデプロイする場合は、そのぶんだけ FIREBASE_PROJECT を変更したcompose.ymlを用意します。

Dockerfileの中身少し解説

ARGで初期値を適当にいれているのは、明示的に指定させないと必ず失敗させるようにしたいためです。

また、compose.ymlでは値を指定しないenvironmentがあると、同名のシステム環境変数から値を取得します。今回のプロジェクトで秘匿したい情報は認証トークンだけですので、これでリポジトリにトークンを書かずに済みます。

DockerfileのCMDで最初にやっていることは、Cloud Functionsのデプロイに必要な設定です。Cloud Functionsを触っていないのであれば、飛ばしても問題ありません。

Circle CI2.0で実行する

Circle CI2.0では、そのままconfig.ymlでcomposeを実行するだけです。
FIREBASE_TOKENはCircle CIの環境変数へ登録してください。ただしVM上での実行を前提とするため、 machine:true を忘れずに設定してください。

.circleci/config.yml
version: 2
jobs:
  build:
    machine: true
    steps:
      - checkout
      - run:
          name: deploy
          command: |
            docker-compose -f ./deploy-develop-compose.yml up --build

機能ごとにリポジトリを分けるべきか

今回紹介したデプロイでは、ひとつのリポジトリにHosting、DatabaseやStorageのRule、Cloud Functionsがすべて入ったリポジトリの場合、全部まるごとデプロイします。当然ながらその場合、不要な部分のデプロイも走ってしまい、無駄な時間ができてしまいます。

いちおう、firebase deploy --only [hosting|functions|database|storage]とすれば部分的なデプロイも可能です。変更点をチェックして、必要な部分デプロイの判断をするやり方を知っているのであれば、その対応をするのが一番かもしれません。
ちなみに私はやり方を知りません…。

そうでない場合、いきなり複数のリポジトリに分けると管理がたいへんな気がするので、いったんはCloud Functionsとその他、で分けるぐらいでも良いかもしれません。

以上です。