Strapi のコンテンツ追加/更新/削除時にVercelのデプロイをする

今回は、Strapi のコンテンツ追加/更新/削除時にVercelのデプロイ処理を実行させる方法についてまとめました。

発端は、Webフロント側をSSGでの実装をしていたために、単にStrapiのコンテンツを追加してもサイトに反映されなかったためです(再デプロイが必要)。これを解決するために、Strapi のコンテンツ追加/更新/削除時にVercelの再デプロイを行う実装をStrapi側に追加しました。

環境

Strapi を開発環境と本番環境のDockerで動かしたい【まとめ版】で VPS上に Strapi 環境を構築しています。

コンテンツを表示させるフロント側のWebサイトは Vercel を利用してデプロイしています。

Vercel の Deploy Hook を利用する

STEP.1
Content-Types に lifecycles.ts を追加する

まずは、追加/更新/削除時に再デプロイを行いたい Content-Types に lifecycles.tsを追加します。

# Strapiのプロジェクトに移動する
$ cd ~/StrapiProjects/vps-docker-strapi

# Hookを利用したい Content-Types に lifecycles.ts を追加する
$ touch src/api/news/content-types/news/lifecycles.ts  

かなり深いですが、この場所にあります。

├── src
│   ├── api
│   │   ├── news(コンテンツ名)
│   │   │   ├── content-types
│   │   │   │   ├── news(コンテンツ名)
│   │   │   │   │   ├── schema.json
│   │   │   │   │   ├── lifecycles.ts (←追加)
STEP.2
ライフサイクルにデプロイ処理を追加する

STEP.1 で追加した lifecycles.tsにデプロイ処理を書きます。

Strapi の lifecycle についてはModels | Strapi Documentationに書かれています。

Vercelへのデプロイについては、「定期実行などに使えそう!VercelのDeploy Hookがとても便利だったという話|カルキチのブログ」の関数を利用させていただきました。

lifecycles.ts
import fetch from 'node-fetch';

module.exports = {
  afterCreate() {
    deployToVercel()
  }, 
  afterUpdate() {
    deployToVercel()
  },
  afterDelete() {
    deployToVercel()
  },
};

async function deployToVercel() {
  const vercelDeployUrl = process.env.VERCEL_DEPLOYMENT_URL;
  if (!vercelDeployUrl) throw new Error('環境変数が設定されていません。');

  await fetch(vercelDeployUrl, { method: 'POST' });
}

かなりシンプルに、コンテンツ作成後/更新後/削除後にデプロイ処理が走るようにできました。

STEP.3
VERCEL_DEPLOYMENT_URL を取得する

Vercelより、対象のサイトの管理画面を開きます。

Settings > Git > Deploy Hooks より Hook を作成します。

STEP.4
デプロイ時のActions に VERCEL_DEPLOYMENT_URL を追加する

STEP.3 で作成したURLを Strapiプロジェクトをデプロイする時に環境変数に設定されるようにします。

このやり方ではない場合は、 .envVERCEL_DEPLOYMENT_URL=STEP.3で取得したURLを配置するといいと思います。

Strapi を開発環境と本番環境のDockerで動かしたい【まとめ版】 – 本番環境へのデプロイのセクションの prod.Dockerfiledeploy.prod.ymlに追記しました。

# Strapiのプロジェクトに移動する
$ cd ~/StrapiProjects/vps-docker-strapi

全体を載せるのでちょっと探しづらいですが# 追加の部分に環境変数を追加しました。

prod.Dockerfile
### deps ステージ ###
FROM node:16 AS deps
RUN apt-get update && apt-get install libvips-dev -y
ENV NODE_ENV=production
WORKDIR /opt/
COPY ./package.json ./yarn.lock ./
ENV PATH /opt/node_modules/.bin:$PATH
RUN yarn config set network-timeout 600000 -g && yarn install

### builder ステージ ###
FROM node:16 AS builder

ENV NODE_ENV=production

WORKDIR /opt/app

ARG HOST
ARG PORT
ARG APP_KEYS
ARG API_TOKEN_SALT
ARG ADMIN_JWT_SECRET
ARG JWT_SECRET
ARG DATABASE_HOST
ARG DATABASE_PORT
ARG DATABASE_NAME
ARG DATABASE_USERNAME
# 追加
ARG VERCEL_DEPLOYMENT_URL

ENV HOST=$HOST
ENV PORT=$PORT
ENV APP_KEYS=$APP_KEYS
ENV API_TOKEN_SALT=$API_TOKEN_SALT
ENV ADMIN_JWT_SECRET=$ADMIN_JWT_SECRET
ENV JWT_SECRET=$JWT_SECRET
ENV DATABASE_HOST=$DATABASE_HOST
ENV DATABASE_PORT=$DATABASE_PORT
ENV DATABASE_NAME=$DATABASE_NAME
ENV DATABASE_USERNAME=$DATABASE_USERNAME
ENV DATABASE_PASSWORD=$DATABASE_PASSWORD
# 追加
ENV VERCEL_DEPLOYMENT_URL=$VERCEL_DEPLOYMENT_URL

# deps ステージでインストールしたライブラリをコピーする
COPY --from=deps /opt/node_modules ./node_modules
COPY . .
RUN yarn build

### runner ステージ ###
FROM node:16 AS runner

WORKDIR /opt/app

ARG HOST
ARG PORT
ARG APP_KEYS
ARG API_TOKEN_SALT
ARG ADMIN_JWT_SECRET
ARG JWT_SECRET
ARG DATABASE_HOST
ARG DATABASE_PORT
ARG DATABASE_NAME
ARG DATABASE_USERNAME
ARG DATABASE_PASSWORD
# 追加
ARG VERCEL_DEPLOYMENT_URL

ENV HOST=$HOST
ENV PORT=$PORT
ENV APP_KEYS=$APP_KEYS
ENV API_TOKEN_SALT=$API_TOKEN_SALT
ENV ADMIN_JWT_SECRET=$ADMIN_JWT_SECRET
ENV JWT_SECRET=$JWT_SECRET
ENV DATABASE_HOST=$DATABASE_HOST
ENV DATABASE_PORT=$DATABASE_PORT
ENV DATABASE_NAME=$DATABASE_NAME
ENV DATABASE_USERNAME=$DATABASE_USERNAME
ENV DATABASE_PASSWORD=$DATABASE_PASSWORD
# 追加
ENV VERCEL_DEPLOYMENT_URL=$VERCEL_DEPLOYMENT_URL

ENV NODE_ENV=production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 strapi

COPY --from=builder /opt/app/public ./public
COPY --from=builder /opt/app/package.json ./package.json
COPY --from=builder /opt/app/favicon.png ./favicon.png

COPY --from=builder --chown=strapi:nodejs /opt/app/dist ./
COPY --from=builder --chown=strapi:nodejs /opt/app/node_modules ./node_modules

EXPOSE 1337

ENV PORT 1337

CMD ["yarn", "start"]
.github/workflows/deploy.prod.yml
name: Deploy to Sakura VPS Production

# main ブランチにプッシュされた時に実行する
on:
  push:
    branches: [main]
  # 手動実行時のログを設定
  workflow_dispatch:
    inputs:
      logLevel:
        description: 'Log level'
        required: true
        default: 'warning'
jobs:
  deploy:
    runs-on: ubuntu-latest
    container: node:16
    steps:
      # チェックアウト
      - uses: actions/checkout@v3
      # Github Container Registry へビルドしてイメージを格納
      - name: Build and Publish to Github Container Registry
        uses: elgohr/Publish-Docker-Github-Action@master
        env:
          HOST: ${{ secrets.STRAPI_HOST }}
          PORT: ${{ secrets.STRAPI_PORT }}
          APP_KEYS: ${{ secrets.APP_KEYS }}
          API_TOKEN_SALT: ${{ secrets.API_TOKEN_SALT }}
          ADMIN_JWT_SECRET: ${{ secrets.ADMIN_JWT_SECRET }}
          JWT_SECRET: ${{ secrets.JWT_SECRET }}
          DATABASE_HOST: ${{ secrets.DATABASE_HOST }}
          DATABASE_PORT: ${{ secrets.DATABASE_PORT }}
          DATABASE_NAME: ${{ secrets.DATABASE_NAME }}
          DATABASE_USERNAME: ${{ secrets.DATABASE_USERNAME }}
          DATABASE_PASSWORD: ${{ secrets.DATABASE_PASSWORD }}
          # 追加
          VERCEL_DEPLOYMENT_URL: ${{ secrets.VERCEL_DEPLOYMENT_URL }}
        with:
          name: GitHubのユーザ名/vps-docker-strapi/vps-docker-strapi-image
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
          dockerfile: prod.Dockerfile
          # 追加
          buildargs: HOST, PORT, APP_KEYS, API_TOKEN_SALT, ADMIN_JWT_SECRET, JWT_SECRET, DATABASE_HOST, DATABASE_PORT, DATABASE_NAME, DATABASE_USERNAME, DATABASE_PASSWORD, VERCEL_DEPLOYMENT_URL
          tags: latest
      # VPSへのデプロイ
      - name: Deploy to Sakura VPS
        uses: appleboy/ssh-action@master
        env:
          GITHUB_USERNAME: ${{ github.actor }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          STRAPI_PORT: ${{ secrets.STRAPI_PORT }}
          DATABASE_HOST: ${{ secrets.DATABASE_HOST }}
          DATABASE_PORT: ${{ secrets.DATABASE_PORT }}
          DATABASE_NAME: ${{ secrets.DATABASE_NAME }}
          DATABASE_USERNAME: ${{ secrets.DATABASE_USERNAME }}
          DATABASE_PASSWORD: ${{ secrets.DATABASE_PASSWORD }}
        with:
          host: ${{ secrets.DEPLOY_PROD_HOST }}
          port: ${{ secrets.DEPLOY_PROD_PORT }}
          username: ${{ secrets.DEPLOY_PROD_USER }}
          key: ${{ secrets.DEPLOY_PROD_KEY }}
          envs: GITHUB_USERNAME, GITHUB_TOKEN, STRAPI_HOST, STRAPI_PORT, APP_KEYS, API_TOKEN_SALT, ADMIN_JWT_SECRET, JWT_SECRET, DATABASE_HOST, DATABASE_PORT, DATABASE_NAME, DATABASE_USERNAME, DATABASE_PASSWORD
          script: |
            docker login ghcr.io -u $GITHUB_USERNAME -p $GITHUB_TOKEN
            docker image pull ghcr.io/GitHubのユーザ名/vps-docker-strapi/vps-docker-strapi-image:latest
            docker container rm -f vps-docker-strapi
            docker container rm -f vps-docker-strapi-postgres
            docker system prune -f
            docker network create vps-docker-strapi
            docker container run --name vps-docker-strapi-postgres -dit --restart=always --net=vps-docker-strapi -p $DATABASE_PORT:5432 -v /srv/vps-docker-strapi/data:/var/lib/postgresql/data -e POSTGRES_DB=$DATABASE_NAME -e POSTGRES_USER=$DATABASE_USERNAME -e POSTGRES_PASSWORD=$DATABASE_PASSWORD -e PGPORT=$DATABASE_PORT postgres
            docker container run --name vps-docker-strapi -dit --restart=always -p $STRAPI_PORT:1337 --net=vps-docker-strapi -v /srv/vps-docker-strapi/public:/opt/app/public --restart=always ghcr.io/GitHubのユーザ名/vps-docker-strapi/vps-docker-strapi-image:latest
STEP.5
GitHub の secrets に追加する

GitHubでStrapiのプロジェクトを開き、STEP.3のURLを secrets に追加します。

Name はVERCEL_DEPLOYMENT_URLで Secrets をSTEP.3のURLに指定します。

STEP.6
Strapi プロジェクトを production にマージする

最後にStrapiの本番環境に設定を反映させます。

production ブランチにマージすると先ほどのdeploy.prod.ymlの Action が実行され本番環境にデプロイされます。

おわりに

Strapi の ライフサイクルはかなり便利そうです。なんでもできそう。

Vercel でデプロイしたフロント側と組み合わせることがあるかはわかりませんが、何かの役に立てば幸いです。