はじめに
Nuxt.jsをFirebaseへデプロイするための開発環境を整えたDockerイメージを作成します。
ちなみにこちらとこちらの記事は、今回の開発環境を作るための前段階になります!
作業環境と作成するDockerイメージの内容は以下になります。
作業環境
- Windows10 pro
- Docker for Windows
作成するDockerイメージの内容
- Alpine Linux
- nvm : v0.33.11
- Node.js : v6.11.5、v8.11.1
- Package Manager : yarn
- Node Modules : create-nuxt-app、firebase-tools
Nuxt.jsの開発環境の構築
(2019-02-21 追記)
create-nuxt-app@2.4.0
ではv8
以降でないとインストールできなくなったようです。
そのため、下記のようにバージョンを分けることで@google-cloud/functions-emulator
をインストールできなくなってしまってしまいました。
もし、ご存知の方がおられましたらご教授いただきたいです!
まずは、Node.jsのv6.11.5
とv8.11.1
をインストールします。
なぜ2つもインストールするかというとNuxt.js
のv2
では、Node.jsのv8
以降が必要になりますが、ローカルでFirebase Functions
の動作確認をするために必要なモジュール@google-cloud/functions-emulator
がv6
以前にしか対応していないためです。
そのため上記モジュールに依存しているfirebase-tools
はv6.11.5
を使ってインストールすることになります。
ちなみにv6.11.5
とv8.11.1
はCloud Functions
が公式に対応しているとされているバージョンになります。
https://cloud.google.com/functions/docs/writing/
https://cloud.google.com/functions/docs/concepts/nodejs-8-runtime
Dockerfileの作成
さて、今回は最初からDockerfileの作成から入ります。
なぜかというとこちらの記事の手順でNode.jsを入れていると時間がかかりすぎるからです・・・
なので、あらかじめビルドされた各バージョンのNode.jsをコピーしてくる方法をとることにします。
それには「Dockerのマルチステージビルド」という機能を使います。
まずはnvmをインストールしたイメージへNode.jsのv6.11.5
とv8.11.1
をインストールしたイメージからビルドしたファイルだけをコピーしてきます。
FROM iidateco/alpine-nvm-node:v6.11.5 as node6
FROM iidateco/alpine-nvm-node:v8.11.1 as node8
FROM iidateco/alpine-nvm
COPY --from=node6 /root/.nvm/versions/node/v6.11.5 /root/.nvm/versions/node/v6.11.5
COPY --from=node8 /root/.nvm/versions/node/v8.11.1 /root/.nvm/versions/node/v8.11.1
ローカルにalpine-nvm
やalpine-nvm-node:v6.11.5
がある場合は、iidateco/
を削除しても大丈夫です。
とりあえずいったんこれでビルドしてみましょう。
PS C:\works\docker\alpine-nuxt-firebase> docker build -t alpine-nuxt-firebase .
イメージからビルドされたNode.jsをコピーしてくるだけなのでそれほど時間はかからないはず・・・。
PS C:\works\docker\alpine-nuxt-firebase> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine-nuxt-firebase latest 3437acd9ced7 10 seconds ago 310MB
iidateco/alpine-nvm-node v8.11.1 f675583791a2 35 minutes ago 266MB
iidateco/alpine-nvm-node v6.11.5 6ba8a1f65a76 2 hours ago 252MB
iidateco/alpine-nvm latest a2f9534c5a8e 4 hours ago 208MB
イメージからコンテナを起動してちゃんとコピーできているかを確認します。
PS C:\works\docker\alpine-nuxt-firebase> docker run --rm -it alpine-nuxt-firebase bash
bash-4.4# nvm ls
v6.11.5
v8.11.1
node -> stable (-> v8.11.1) (default)
stable -> 8.11 (-> v8.11.1) (default)
iojs -> N/A (default)
コピーしただけではデフォルトのNode.jsのバージョンが設定されていないので設定します。
Nuxt.js
のv2
で開発したいのでデフォルトはv8.11.1
にしておきます。
bash-4.4# nvm alias default 8.11.1
default -> 8.11.1 (-> v8.11.1)
ただし、firebase-tools
はv6.11.5
を使ってインストールするので、以下のようにします。
また、パッケージマネージャはyarn
を使いたいのでapk add
でインストールします。
bash-4.4# apk update
bash-4.4# apk add --no-cache yarn
bash-4.4# yarn global add create-nuxt-app firebase-tools
(create-nuxt-app@2.4.0
がv8
以降でないとインストールできなくなったのでnvm use 6.11.5
の記述を削除しました。)
適当なディレクトリで動作確認します。
実行前にNode.jsをv8.11.1
に変えておきます。
bash-4.4# nvm use 8.11.1
bash-4.4# cd /tmp
bash-4.4# create-nuxt-app sample
> Generating Nuxt.js project in /tmp/sample
? Project name sample
? Project description My lovely Nuxt.js project
? Use a custom server framework none
? Use a custom UI framework none
? Choose rendering mode Universal
? Use axios module no
? Use eslint no
? Use prettier no
? Author name
? Choose a package manager yarn
ちゃんと動きました!
最後のパッケージマネージャだけyarn
にしてあとはデフォルトにしてあります。
試しに動かしてみます。
bash-4.4# cd sample/
bash-4.4# yarn dev
ローカルサーバがhttp://localhost:3000
に立ち上がりますが、ブラウザからアクセス出来ないと思います。
なぜならホストOSとゲストOSのポートの紐付けができていないからです。
またゲストOSの環境変数の設定も必要になります。
以下のコマンドで先ほど作ったイメージを起動し直してみます。
PS C:\works\docker\alpine-nuxt-firebase> docker run --rm -it -e HOST=0.0.0.0 -p 3000:3000 alpine-nuxt-firebase bash
-e
オプションで環境変数、-p
オプションでポートの紐付けができます。
これで立ち上がったコンソールで先ほどの手順をもう一度行ってみましょう。
コンソールにはhttp://127.0.0.1:3000
と表示されますが、http://localhost:3000
をブラウザで表示してください。
今度はちゃんと表示されたかと思います!
では上記の手順をDockerfileに追記しましょう。
FROM iidateco/alpine-nvm-node:v6.11.5 as node6
FROM iidateco/alpine-nvm-node:v8.11.1 as node8
FROM iidateco/alpine-nvm
COPY --from=node6 /root/.nvm/versions/node/v6.11.5 /root/.nvm/versions/node/v6.11.5
COPY --from=node8 /root/.nvm/versions/node/v8.11.1 /root/.nvm/versions/node/v8.11.1
SHELL ["/bin/bash", "-c"]
RUN \
source ~/.bashrc && \
apk update && \
apk add --no-cache yarn && \
nvm alias default 8.11.1 && \
nvm use 6.11.5 && \
yarn global add create-nuxt-app firebase-tools && \
yarn cache clean
ENV HOST 0.0.0.0
環境変数の指定はDockerfileでできますが、ポートの紐付けはDockerfileではできないので、引き続き起動コマンドで指定するようにします。
上記のファイルで再度イメージをビルドします。
Dockerfileで環境変数を指定したので起動のコマンドは以下のようになります。
PS C:\works\docker\alpine-nuxt-firebase> docker run --rm -it -p 3000:3000 alpine-nuxt-firebase bash
Nuxt.jsの動作確認をしてちゃんと動いたら成功です!
ホストOSのディレクトリのマウント
さて、これからFirebaseでSSRするための設定をしていくのですが、いままで使っていた起動コマンドでは作業がやりにくいので、以下のようにコマンドを変えます。
PS C:\works\project> docker run -it --mount type=bind,src=$(pwd)/,dst=/project -p 3000:3000 -p 5000:5000 -p 5001:5001 alpine-nuxt-firebase bash
上記コマンドをプロジェクトを作成したいディレクトリまでコンソールで移動してから実行してください。
いままで--rm
オプションをつけていましたが、これだとDockerコンテナを終了したときにコンテナが削除されてしまい、作業途中のデータが無くなってしまうのでなくしました。
--mount type=bind,src=$(pwd)/,dst=/project
のオプションで、コマンド実行ディレクトリが/project
にマウントされるようになります。
では、起動したコンテナのコンソールで/project
に移動して、create-nuxt-app
でプロジェクトを作成してみましょう。
ホストOSのマウントしたディレクトリにもファイルが作成されていると思います。
FirebaseでSSRするための設定
Dockerの環境はこれで準備ができたので、Nuxt.jsをFirebaseでSSRするための設定をしていきます。
こちらにサンプルを用意しましたので、それを参照しながら要点を説明していきます。
https://github.com/iida-teco/nuxt-firebase-ssr-template.git
ディレクトリ構成
Nuxt.jsをFirebaseでSSRするためにはHosting
にアップロードするものとFuncstions
にアップロードするものに分けることになります。
app
というディレクトリがNuxt.jsのファイルがおいてあります。
functions
というディレクトリにはFunctions
で使われるファイルがおいてあります。
以下のようなディレクトリ構成になっていると思います。
bash-4.4# ls -la
total 226
drwxrwxrwx 2 root root 4096 Dec 6 02:27 .
drwxrwxrwx 2 root root 0 Dec 6 02:23 ..
-rwxr-xr-x 1 root root 207 Dec 3 08:20 .editorconfig
drwxrwxrwx 2 root root 0 Dec 6 02:27 .git
-rwxr-xr-x 1 root root 1214 Dec 6 08:03 .gitignore
-rwxr-xr-x 1 root root 291 Dec 6 02:29 README.md
drwxrwxrwx 2 root root 0 Dec 3 08:43 app
-rwxr-xr-x 1 root root 272 Dec 6 02:21 firebase.json
drwxrwxrwx 2 root root 0 Dec 3 09:55 functions
-rwxr-xr-x 1 root root 889 Dec 6 02:19 nuxt.config.js
-rwxr-xr-x 1 root root 666 Dec 6 08:04 package.json
-rwxr-xr-x 1 root root 210503 Dec 3 08:22 yarn.lock
bash-4.4# ls -la app/
total 4
drwxrwxrwx 2 root root 0 Dec 3 08:43 .
drwxrwxrwx 2 root root 4096 Dec 6 02:27 ..
drwxrwxrwx 2 root root 0 Dec 3 08:20 assets
drwxrwxrwx 2 root root 0 Dec 3 08:20 components
drwxrwxrwx 2 root root 0 Dec 3 08:20 layouts
drwxrwxrwx 2 root root 0 Dec 3 08:20 middleware
drwxrwxrwx 2 root root 0 Dec 3 08:20 pages
drwxrwxrwx 2 root root 0 Dec 3 08:20 plugins
drwxrwxrwx 2 root root 0 Dec 3 08:20 static
drwxrwxrwx 2 root root 0 Dec 3 08:20 store
bash-4.4#
bash-4.4# ls -la functions/
total 6
drwxrwxrwx 2 root root 0 Dec 3 09:55 .
drwxrwxrwx 2 root root 4096 Dec 6 02:27 ..
-rwxr-xr-x 1 root root 619 Dec 6 02:20 index.js
-rwxr-xr-x 1 root root 496 Dec 6 02:22 package.json
ビルド設定の変更
nuxt.config.js
で設定を行います。
変更箇所を抜粋して説明します。
module.exports = {
mode: 'universal',
srcDir: 'app',
buildDir: 'functions/nuxt',
}
srcDir
にNuxt.jsのファイルがおいてある場所を指定します。
buildDir
にビルドした結果を出力する先を指定します。
Cloud Functions
でビルドした結果のファイルを参照するためfunctions
以下に出力します。
/*
** Build configuration
*/
build: {
publicPath: '/assets/',
/*
** You can extend webpack config here
*/
extend(config, ctx) {
}
}
}
publicPath
にはCDNのURLを記述するようなのですが、ビルドしたファイルをhosting
しているディレクトリ(後述)を指定します。
ビルド後のファイルの配置をpackage.json
で設定します。
"scripts": {
"setup": "yarn && cd functions && yarn && cd ..",
"dev": "nuxt dev",
"start": "nuxt start",
"generate": "nuxt generate",
"build": "nuxt build && yarn build:copy",
"build:copy": "rimraf public/* && cpx \"functions/nuxt/dist/**/*\" public/assets/ && cpx \"app/static/*\" public/",
"serve": "firebase serve --host 0.0.0.0",
"deploy": "firebase deploy"
},
build:copy
でビルドされたファイルをpublic
にコピーしています。
このディレクトリがFirebaseにhosting
されます。
Firebaseの設定
firebase.json
で設定します。
{
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"function": "ssr"
}
]
},
"functions": {
"source": "functions"
}
}
public
にはhosting
するディレクトリを指定します。
rewrites
ですべてのアクセスをssr
ファンクションへリダイレクトするようにしておきます。
functions
にはCloud Functions
で動かすディレクトリを指定します。
SSRするファンクションの説明
functions/index.js
がSSRするファンクションになります。
const functions = require('firebase-functions');
const express = require('express');
const { Nuxt } = require('nuxt');
const app = express();
const nuxt = new Nuxt({
dev: false,
buildDir: 'nuxt',
build: {
publicPath: '/assets/'
}
});
function handleRequest(req, res) {
res.set('Cache-Control', 'public, max-age=600, s-maxage=1200');
return new Promise((resolve, reject) => {
nuxt.render(req, res, (promise) => {
promise.then(resolve).catch(reject);
});
});
}
app.use(handleRequest);
exports.ssr = functions.https.onRequest(app);
express
を使って、Nuxt.jsがレンダリングした結果を表示します。
Nuxt.jsを初期化する祭に指定するオプションはnuxt.config.js
と同じ内容にします。
buildDir
だけは階層が違うのでnuxt
だけにしてあります。
また、functions/package.json
に以下の設定を入れておきます。
"engines": {
"node": "8"
},
これはNode.jsをv8
で動かすという設定になります。
以上がNuxt.jsをFirebaseでSSRするための変更の要点になります。
Firebaseのローカル環境で実行
実行する前にFirebaseへのログインとプロジェクトの紐付けをします。
Firebaseのアカウントを作成してFirebaseコンソールで適当にプロジェクトを作成してください。
まずはFirebaseにログインします。
ゲストOSのコンソールでのログインは、ブラウザに連携できないのでURL経由でのログインになります。
bash-4.4# firebase login --no-localhost
上記コマンドで表示されるURLをブラウザで開くとアカウントの選択と権限の許可リクエストが表示されるので適宜選択すると、認証コードが表示されます。
その認証コードをコンソールに入力すればログイン完了です。
ログインできたら作ったプロジェクトを紐付けします。
bash-4.4# firebase use --add
上記コマンドを入力するとプロジェクトの選択肢が表示されるので、作ったプロジェクトを選択します。
次のalias
の指定は適当にdefault
でも指定してください。
これで準備ができたので実行してみます!
package.json
のscript
にserve
コマンドを用意しておいたので、以下を実行すれば開始されます。
firebase serve
に--host 0.0.0.0
オプションをつけないとホストOSのブラウザからアクセスできませんでした。
bash-4.4# yarn setup
bash-4.4# yarn build
bash-4.4# yarn serve
初回だとyarn setup
に結構時間がかかると思います。
bash-4.4# yarn serve
yarn run v1.7.0
$ firebase serve --host 0.0.0.0
=== Serving from '/project/nuxt-firebase-ssr-template'...
i functions: Preparing to emulate functions.
Warning: You're using Node.js v8.11.1 but Google Cloud Functions only supports v6.11.5.
i hosting: Serving hosting files from: public
✔ hosting: Local server: http://0.0.0.0:5000
✔ functions: ssr: http://0.0.0.0:5001/fana-dev/us-central1/ssr
上記のように表示されたら起動完了なのですが、http://0.0.0.0:5000
ではなくhttp://localhost:5000
をブラウザで開くと表示が確認できると思います。
これでNuxt.jsをFirebaseでSSRできるようになりました!
あとは、yarn deploy
でFirebaseにデプロイして動作を確認してみてください!
ハマった箇所
今回使用したサンプルでは出てきませんでしたが、環境を作っていてハマった箇所を共有しておきます。
-
package.json
のdependencies
にあるモジュールはfunctions/package.json
のdependencies
にも書いておく必要がある
これ、ローカル環境では動くのですが、Firebaseにデプロイすると動かなくなるので、原因が特定できなくて苦労しました。
- Docker for Windowsではホットリロードが機能しない
ホストOSでファイルを書き換えても自動でリロードしてくれません。
nuxt.config.js
に以下を記述することで対応できます。
watchers: {
webpack: {
poll: true
}
},
まとめ
Dockerの環境構築は初めてだったので手探りでやってきました。
今回のDockerの構成も、もっとスマートにできるかもしれません。
これまでの記事でDockerでの開発環境の構築方法などの手助けになれば幸いです!
FirebaseとNuxt.jsも今回初めて触りましたが、いろいろ気が利いてて使いやすいサービスとフレームワークだと思いました。
次はこの構成でなにか動くものを作りたいと思います!
参考にしたサイト
Use multi-stage builds
Dockerのマルチステージビルドを使う
Container networking
nuxt.js + firebase (cloud functions) で最小構成SSR
Create a SSR + Serverless App with Firebase & Nuxt.js within an hour
IT研修でVuePress+Express+Nuxt on Dockerでシステムを作成した話
Hot reloading doesn't work in docker container on Windows