🚍

CaddyとCloudflarePagesで複数サイトを1レポジトリでデプロイ

2023/10/14に公開

都知事杯ハッカソン2023でファイナルステージに進んだ、BuTTERというツールを運営しています。BuTTERでは、別ドメインの3つのサイトを1つのレポジトリで管理することで効率的な開発・運用を行っており、これら3つのサイトは一括でCloudflare Pagesにてホストし、各ドメインへのアクセスをCaddy Proxyにより振り分けています。この記事では、その運用方法を共有させていただきます。

運用しているサイト

BuTTERでは、以下のような3つのサイトを運用しています。

URL 用途
https://butter.takoyaki3.com BuTTER全体の紹介
https://tag-maker.butter.takoyaki3.com Butterタグを作成するためのページ
https://timetable.butter.takoyaki3.com BuTTERを使った時刻表サイト

アーキテクチャと設計方針

これらは1つのGitレポジトリでコードを管理しており、CloudflareのPagesに最新のproductブランチがデプロイされる仕組みを構築しています。

このレポジトリは関係するこれら3サイト部分のみ取り出すと以下のような構成となっており、それぞれのサイトは次のようなURLでCloudflarePageでホストされているサイトに直接アクセスすることもできます。

URL CloudflarePagesへ直接アクセスする場合のURL 用途
https://butter.takoyaki3.com https://butter.pages.dev/ BuTTER全体の紹介
https://tag-maker.butter.takoyaki3.com https://butter.pages.dev/tag-maker Butterタグを作成するためのページ
https://timetable.butter.takoyaki3.com https://butter.pages.dev/timetable BuTTERを使った時刻表サイト

レポジトリにおけるフォルダ構成

このレポジトリには、サイトごとに独立したフォルダがあります。

├── about
│   ├── babel.config.js
│   ├── docker-compose.yml
│   ├── Dockerfile
│   ├── jsconfig.json
│   ├── package.json
│   ├── package-lock.json
│   ├── public
│   ├── README.md
│   ├── src
│   └── vue.config.js
├── tag-maker
│   ├── babel.config.js
│   ├── docker-compose.yml
│   ├── Dockerfile
│   ├── jsconfig.json
│   ├── package.json
│   ├── package-lock.json
│   ├── public
│   ├── README.md
│   ├── src
│   └── vue.config.js
└── timetable-app
    ├── babel.config.js
    ├── docker-compose.yml
    ├── Dockerfile
    ├── jsconfig.json
    ├── package.json
    ├── package-lock.json
    ├── public
    ├── README.md
    ├── src
    └── vue.config.js

Caddyによるリバースプロキシの設定

これらは、*.butter.takoyaki3.comへのアクセスをすべてDNS設定によりCaddyがインストールされたVPSに割り当てており、Caddyのリバースプロキシ機能によりCloudflarePagesに転送しています。Caddyfileの各設定は以下のようになっています。ここでは、header_upを使ってリバースプロキシ先のヘッダを書き換え、rewrite指令でURLの書き換えも行っています。なお、 https://butter.takoyaki3.com/v0.0.0/root.json というROOTファイルのみ頻繁に自動更新されるため、Pagesとは別のホスト先に転送する設定を施しています。

timetable.butter.takoyaki3.com {
  rewrite * /timetable{uri}
  reverse_proxy https://butter.pages.dev {
    header_up Host {http.reverse_proxy.upstream.hostport}
  }
    log {
      output file /log/timetable.butter.takoyaki3.com.log {
        roll_size 10MiB
        roll_keep 128
        roll_keep_days 30
        roll_suffix .20060102-15
        output_format pretty_json
      }
    }
}

tag-maker.butter.takoyaki3.com {
  rewrite * /tag-maker{uri}
  reverse_proxy https://butter.pages.dev {
    header_up Host {http.reverse_proxy.upstream.hostport}
  }
    log {
      output file /log/tag-maker.butter.takoyaki3.com.log {
        roll_size 10MiB
        roll_keep 128
        roll_keep_days 30
        roll_suffix .20060102-15
        output_format pretty_json
      }
    }
}

butter.takoyaki3.com {
  handle /v0.0.0/root.json {
    reverse_proxy https://pub-ad1f4a48b8ef46779b720e734b0c2e1d.r2.dev {
      header_up Host {http.reverse_proxy.upstream.hostport}
    }
  }
  reverse_proxy https://butter.pages.dev {
    header_up Host {http.reverse_proxy.upstream.hostport}
  }
    log {
      output file /log/butter.takoyaki3.com.log {
        roll_size 10MiB
        roll_keep 128
        roll_keep_days 30
        roll_suffix .20060102-15
        output_format pretty_json
      }
    }
}

ビルド設定

このようなサイト構成でデプロイするために、ビルド時に、about,tag-maker,timetable-appをそれぞれビルドする際に、ビルドしたディレクトリがそれぞれdist,dist/tag-maker,dist/timetable-appになるように、ビルドしたファイルの保存先などを設定するvue.config.jsにそれぞれ設定しています。この設定により、ビルドされたファイルはそれぞれ適切な場所に保存されます。

aboutにおけるvue.config.js

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  outputDir: '../dist/',
  transpileDependencies: [
    'vuetify'
  ]
})

tag-makerにおけるvue.config.js

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  outputDir: '../dist/tag-maker',
  transpileDependencies: [
    'vuetify'
  ]
})

timetable-appにおけるvue.config.js

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  outputDir: '../dist/timetable',
  transpileDependencies: [
    'vuetify'
  ]
})

自動ビルド設定

これらの設定を施した上で、package.json内でnpm run buildした際にビルド用にbuild.jsが実行されるように指定し、build.jsでは先ほどのabout,tag-maker,timetable-appの3つのディレクトリを1つのコマンドでビルドするように設定しています。

package.jsonの関連する部分

{
  "scripts": {
    "build": "node build.js"
  },
  "dependencies": {
    "butter-lib": "^1.0.10"
  }
}

build.js

const { exec } = require('child_process');
const util = require('util');
const execAsync = util.promisify(exec);

const dirs = ['./about', './timetable-app', './tag-maker']; // ビルドするディレクトリのリスト

const buildDir = async (dir) => {
  try {
    console.log(`依存パッケージのインストール開始: ${dir}`);
    await execAsync('npm install', { cwd: dir });
    console.log(`依存パッケージのインストール成功: ${dir}`);

    console.log(`ビルド開始: ${dir}`);
    await execAsync('npm run build', { cwd: dir });
    console.log(`ビルド成功: ${dir}`);
  } catch (err) {
    console.error(`エラー: ${dir}`, err);
  }
};

Promise.all(dirs.map(dir => buildDir(dir)))
  .then(() => console.log('全てのビルドが完了しました'))
  .catch(err => console.error('ビルド中にエラーが発生しました', err));

Cloudflare Pagesの設定

最後に、Cloudflare Pagesでdistディレクトリを公開するように設定しています。これにより、1つのレポジトリで複数のサイトを効率よく運用しています。

まとめ

このようにして、BuTTERは1つのレポジトリで複数のサイトを効率よく運用しています。このアーキテクチャを採用することで、開発のスピードと効率が大幅に向上しています。

Discussion