Skip to content

Commit 499a479

Browse files
authored
Merge pull request #1710 from future-architect/shibukawa-patch-1
Clarify Docker usage for secure Vue.js deployment
2 parents bceecf6 + 6818ae6 commit 499a479

File tree

1 file changed

+19
-18
lines changed

1 file changed

+19
-18
lines changed

source/_posts/2025/20251023a_Vueのフロントエンドをセキュリティのしっかりしたコンテナにする.md

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ lede: "Next.jsやNuxt.jsなどのサーバーサイドレンダリング必須
1919

2020
Next.jsやNuxt.jsなどのサーバーサイドレンダリング必須なフレームワークであれば、Node.jsと一緒にコンテナ化するか、Vercelなどにデプロイする方法があります。こちらはJavaScriptのウェブアプリケーションなので実行環境を用意する必要があります。
2121

22-
一方、SPAとして作成したVue.jsなど、現代のフレームワークで作成したフロントエンドは、ビルドすると静的HTMLとJavaScriptコードになります。ただし、ファイルが存在指定なパスへのリクエストがあった場合にindex.htmlの内容をフォールバックとして返す必要があります
22+
一方、SPAとして作成したVue.jsなど、現代のフレームワークで作成したフロントエンドは、ビルドすると静的HTMLとJavaScriptコードになります。ただし、物理的なファイルが存在しないパスへのリクエストがあった場合にindex.htmlの内容をフォールバックとして返す必要があるため、動作させるにはそのあたりが設定可能なウェブサーバーを使う必要があります。index.htmlをロードするとブラウザ上でJavaScriptのコードが動作しますが、そのコードが内部で持っているURLのパス情報をみて適切なページを表示したり、それでも存在なければJavaScriptがエラー画面を出力します
2323

24-
[Webフロントエンド設計ガイドラインのSPAのホスティング](https://future-architect.github.io/arch-guidelines/documents/forWebFrontend/web_frontend_guidelines.html#spaのホスティング)では、いくつか紹介しています
24+
フューチャー作のガイドラインの[Webフロントエンド設計ガイドラインのSPAのホスティング](https://future-architect.github.io/arch-guidelines/documents/forWebFrontend/web_frontend_guidelines.html#spaのホスティング)では、いくつかホスティング方法を紹介しています
2525

2626
1. CloudFront+S3
2727
2. LB+S3
@@ -31,17 +31,17 @@ Next.jsやNuxt.jsなどのサーバーサイドレンダリング必須なフレ
3131

3232
* classmethodブログ: [Application Load Balancer のリスナールールでトランスフォームを構成し、ターゲットにルーティングする前にホストヘッダーや URL パスを書き換えれるようになりました](https://dev.classmethod.jp/articles/application-load-balancer-url-header-rewrite/)
3333

34-
これ以外には、ウェブアプリケーション側に配信機能を持たせてしま鵜というのも過去に技術ブログで紹介しました。比較的高速なGoとかRustならありでしょう
34+
これ以外には、ウェブアプリケーション側に配信機能を持たせてしまうというのも過去に技術ブログで紹介しました。比較的高速なGoとかRustならこれもありでしょう
3535

3636
* [Go 1.16のembedとchiとSingle Page Application](https://future-architect.github.io/articles/20210408/)
3737

38-
今回はDockerイメージを作る、3番目の方法を試そうと思います
38+
今回はガイドラインではSPA用によいとしている1. 3のうち、Dockerイメージを作った3番目の方法を全力で試そうと思います
3939

4040
# なぜDockerにするか
4141

42-
S3とかオブジェクトストレージにおいて配信というのがお手軽ですが、コンテナにまとめておくことでデプロイ時にまとめてフロントエンド資材を入れ替えたりがしやすいのがメリットと考えています。また、CloudFrontはインターネット公開するサービスには良いのですが、社内システムでは使えません。また、ビルド済みフロントエンドを軽量なサーバーで配信すればリソース消費は少なくて済みます。開発時もフロントエンドを触らない人にとってはありがたいのではないでしょうか
42+
S3とかオブジェクトストレージにおいて配信というのがお手軽ですが、コンテナにまとめておくことでデプロイ時にまとめてフロントエンド資材を入れ替えたり、戻したりがしやすいのがメリットと考えています。また、CloudFrontはインターネット公開するサービスには良いのですが、社内システムでは使えません。また、ビルド済みフロントエンドを軽量なサーバーで配信すればリソース消費は少なくて済みます。開発時もフロントエンドを触らない人がローカルで動作検証するにはありがたいでしょう。
4343

44-
せっかく作るのであればセキュリティを意識したコンテナを目指します。近年、ランサムウェアが流行っています。静的なHTML/JSでアプリを作りフロントエンドを配信するだけのコンテナにすることで攻撃面をかなり狭くできます。ですが不正なプログラムを配る踏み台にはされる可能性があるため、次の項目にもチャレンジしてみます
44+
せっかく作るのであればセキュリティを意識したコンテナを目指します。近年、ランサムウェアが流行っています。静的なHTML/JSでアプリを作りフロントエンドを配信するだけのコンテナにしてバックエンドをプライベートネットワークの後ろ側に隠すことで攻撃面をかなり狭くできます。ですがHTMLなどが買い替えられると不正なプログラムを配る踏み台にされる可能性があるため、そうならないために次の項目にもチャレンジしてみます
4545

4646
* シェルがないDistroless
4747
* フロントエンドのartifactは読み込み専用で実行ユーザーでは書き換えられない
@@ -56,7 +56,6 @@ Need to install the following packages:
5656
5757
Ok to proceed? (y) y
5858

59-
6059
> npx
6160
> "create-vite"
6261

@@ -70,7 +69,7 @@ Ok to proceed? (y) y
7069
:
7170
```
7271

73-
シングルページアプリケーションで正しく動作することをテストするために、vue-routerを入れてページをいくつか足します。
72+
シングルページアプリケーションとして正しく動作することをテストするために、vue-routerを入れてページをいくつか足します。
7473

7574
```ts router/index.ts
7675
import { createRouter, createWebHistory } from 'vue-router'
@@ -178,14 +177,13 @@ h1 { margin-bottom: 1rem }
178177

179178
## nginxの設定
180179

181-
Vueアプリができたところで次はサーバーです。Rust製の[static-web-server](https://crates.io/crates/static-web-server)とか安全そうだし良さそうだなとも思ったのですが、[APIサーバーへのリクエストをプロキシするような設定がなく、今後も入らなそう](https://github.com/static-web-server/static-web-server/issues/489)ということもあり見送りました。本番デプロイだけならALBがやってくれるはずなのでstatic-web-serverでも良いかと思います。まあありきたりですがnginxにします
180+
Vueアプリができたところで次はサーバーです。Rust製の[static-web-server](https://crates.io/crates/static-web-server)とか安全そうだし良さそうだなとも思ったのですが、[APIサーバーへのリクエストをプロキシするような設定がなく、今後も入らなそう](https://github.com/static-web-server/static-web-server/issues/489)ということもあり見送りました。このプロキシ機能があればウェブフロントエンドとバックエンドが同じドメイン(ポート番号も含めて)動作するので、CORSを機にする必要がなくなります。もちろん、作ったイメージを本番デプロイするだけならALBがやってくれるはずなのでstatic-web-serverでも良いかと思います。ここはありきたりですがnginxにしておきます。なお、今回はイメージサイズは60MBほどになりました。static-web-serverはシングルバイナリで4MBほどらしいので小ささを極めたい場合はstatic-web-serverで試すと良いでしょう
182181

183182
設定ファイルとしては、SPAで必要なフォールバックを入れたのと、ログは`/var/log`とかではなく、コンソールに出力するようにしています。
184183

185-
設定ファイル上に`user www-data;`と書けばユーザーが設定できます。ただし設定しなくても`nobody`ユーザーで動作します。最初は非ルートでやるぞ!と設定していたのですが、`nobody`で十分です
184+
設定ファイル上に`user www-data;`と書けばユーザーが設定できます。ただし設定しなくてもワーカーは`nobody`ユーザーで動作します。最初はセキュリティ強化のためにサーバーは非ルートユーザーで動かすぞ!と設定していたのですが、ワーカーさえルートでなければ実用上は問題ないため、無視底の`nobody`で十分だと判断しました。なお、別ユーザーで動かすと、特権ポートの1024以下は使えないため、80番ポートでサーバーを動かすことはできなくなります
186185

187186
```sh nginx.conf
188-
# nginx.conf
189187

190188
worker_processes auto;
191189

@@ -234,20 +232,18 @@ http {
234232

235233
## Dockerfile
236234

237-
Dockerfileは以下の通りです。最初Geminiに雛形をざっと作ってもらいましたが、いろいろ細かいところを後から修正しました。その際に心がけたポイントは以下の通りです
235+
Dockerfileは以下の通りです。最初Geminiに雛形をざっと作ってもらいましたが、いろいろ細かいところを後から修正しました。その際に心がけたポイントは以下の通りです
238236

239237
1. ビルドステージ
240-
* bindマウント、cacheマウントを駆使してキャッシュフレンドリーな高速ビルド
238+
* bindマウント、cacheマウントを駆使してキャッシュフレンドリーな高速ビルド(生成AIはいつもやってくれない)
241239
2. nginxの設定
242240
* こちらもbindマウント、cacheマウントで効率化
243-
* 最終イメージのDistrolessはシェルがなくてmkdirとかもできないので、こちらのステージですべての必要なフォルダを作ったり、ユーザーやグループの設定を引っこ抜いたり、ディレクトリの権限設定をお子なり必要ライブラリをコピーしたりも含めて全て行なっています
241+
* 最終イメージのDistrolessはシェルがなくてmkdirとかもできないので、こちらのステージですべての必要なフォルダを作ったり、ユーザーやグループの設定を引っこ抜いたり、ディレクトリの権限設定を行ったり、nginxの動作に必要ライブラリをコピーしたりも含めて全て行なっています
244242
3. 実行イメージ
245243
* Debianの新しいバージョンのtrixie(13)が使いたい→まだベータ扱いなのでいったん保留
246244
* ビルド済みのHTML/JSを持ってきたり、nginxの設定を持ってきたり
247245
* 実行ユーザー(nobodyから書き換えられないユーザーでHTML/JSを配置
248246

249-
最初は実行イメージは別ユーザー、と思ったのですが、nginx自体、ワーカーを作ってそちらが実質的な処理を行うと言う個性になっており、それはnobodyで動きます。そのため、仮にプロセスが乗っ取られても被害はないかな、ということでやっています。
250-
251247
```Dockerfile Dockerfile
252248
# syntax=docker/dockerfile:1
253249

@@ -321,6 +317,8 @@ EXPOSE 80
321317
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
322318
```
323319

320+
実行するには次のようにします。
321+
324322
```sh
325323
# ビルド
326324
$ docker build -t vue-spa .
@@ -331,10 +329,13 @@ $ docker run --rm -it -p 8080:80 vue-spa
331329

332330
## デバッグ実行
333331

334-
実行イメージを`gcr.io/distroless/base-debian12`から`gcr.io/distroless/base-debian12:debug`にして、
332+
設定を変えてみたい場合のデバッグ方法も紹介しておきます。Distrolessではセキュリティのためにシェルがイメージに含まれていませんが、デバッグ用のイメージが提供されています。
333+
334+
* ランタイムをイメージを`gcr.io/distroless/base-debian12`から`gcr.io/distroless/base-debian12:debug`に変更
335+
* `docker run --rm -it -p 8080:80 --entrypoint=sh vue-spa` で、shをnginxの代わりに実行
335336

336337
# まとめ
337338

338-
Dockerfileは新旧の書き方がウェブには混在しているため、機会をみてはbind/cacheを使ったDockerfileをブログに書くようにしています。今回はSPAの静的HTMLのコンテナを考察して作ってみました。ビルドの効率と実行効率、セキュリティ、どれも妥協しないDockerfileを作りました。
339+
Dockerfileは新旧の書き方がウェブには混在しているため、機会をみてはbind/cacheを使ったモダンなDockerfileの記法をブログに書くように日頃からしていました。今回はVue連載ということで、Vue製のSPAの静的HTMLのコンテナを作ってみました。単に作るだけでは世の中の有象無象の記事と変わらないので、最新の記法を使ったビルドの効率、実行効率、セキュリティ、どれも妥協しないDockerfileを作りました。Vue以外の方にも参考にしてもらえる記事になったと思います
339340

340341
明日は松本朝香さんです。

0 commit comments

Comments
 (0)