【自作ブログ】(1) - StrapiをHerokuにデプロイする

 前々から自分でブログシステムを構築したいと思っていた。
 はてなブログを始めたのはブログというアウトプットが自分に向いているのかどうかを確かめるためだ。そして一年以上はてなブログを続けてある程度ブログを書くという習慣はついたように思える。
 そろそろ最初の夢を追いかけても良いだろう。あと、もっとtechカテゴリーを増やしたい。

 最初はちゃんと全体の構成を考えて、開発環境と本番環境も事前にきっちり準備して、CI/CDももちろん用意するつもりだったけど、それすると一生設計する気がした、というか実際ずっと頭の中で考えてなかなか手を動かせなかったので、もうとりあえずデプロイするかってなった。

 CMSの方をどうするか色々考えたがStrapiを利用することにした。


プロジェクト作成

 さて、Strapiプロジェクトを作成する。Quick Start GuideによるとNode20をサポートしているようだ。Node20のDockerイメージをpullしてコンテナ上でcreate-strapi-appコマンドを実行する。

% docker container run --rm -it -v $PWD:/tmp/strapi -w /tmp/strapi/ node:20.10.0 npx create-strapi-app@4.16.2 <application name> --typescript
Need to install the following packages:
create-strapi-app@4.16.2
Ok to proceed? (y) y
? Choose your installation type Custom (manual settings)
? Choose your default database client postgres
? Database name: strapi
? Host: 127.0.0.1
? Port: 5432
? Username: admin
? Password: ********
? Enable SSL connection: No

Creating a project with custom database options.
Creating a new Strapi application at /tmp/strapi/<application name>.
Creating files.
Dependencies installed successfully.

Your application was created at /tmp/strapi/<application name>.

Available commands in your project:

  yarn develop
  Start Strapi in watch mode. (Changes in Strapi project files will trigger a server restart)

  yarn start
  Start Strapi without watch mode.

  yarn build
  Build Strapi admin panel.

  yarn strapi
  Display all available commands.

You can start by doing:

  cd /tmp/strapi/<application name>
  yarn develop

npm notice
npm notice New patch version of npm available! 10.2.3 -> 10.2.5
npm notice Changelog: https://github.com/npm/cli/releases/tag/v10.2.5
npm notice Run npm install -g npm@10.2.5 to update!
npm notice

 最終的にHerokuにデプロイしようと考えていたので、データベースにはPostgresを選択した。

 まずローカル環境(PC)においてDockerで動作確認する。
 docker-compose.yamlを用意。DB用のコンテナとAPI用のコンテナを起動する。

% cat docker-compose.yaml
version: '3'
services:
  postgres:
    image: postgres:15.5
    container_name: 'db'
    ports:
      - 5432:5432
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: password
      POSTGRES_DB: strapi
    volumes:
      - ./db/data:/var/lib/postgresql/data
  app:
    image: node:20.10.0
    container_name: 'app'
    ports:
      - 1337:1337
    volumes:
      - ./:/tmp/strapi/
    working_dir: /tmp/strapi
    tty: true
    depends_on:
      - postgres

 DB用ディレクトリも作成する。

% mkdir -p db/data

 .envファイルを修正。DATABASE_HOSTpostgresに修正してAPI用コンテナからDB用コンテナに接続できるようにする。

...
# Database
DATABASE_CLIENT=postgres
# DATABASE_HOST=127.0.0.1
DATABASE_HOST=postgres
...

 コンテナを起動。

% docker-compose up -d
[+] Running 3/3
 ✔ Network xxx_default        Created                                                                                                                                                                                                           0.1s
 ✔ Container xxx-postgres-db  Started                                                                                                                                                                                                           0.5s
 ✔ Container xxx-app          Started                                                                                                                                                                                                           5.2s

 API用コンテナにログインし、npm run developを実行する。

% docker-compose exec app /bin/bash 

root@67d28cb267ce:/tmp/strapi# npm run develop

> xxx-strapi@0.1.0 develop
> strapi develop

✔ Cleaning dist dir (19ms)
⠋ Building build context
[INFO] Including the following ENV variables as part of the JS bundle:
    - ADMIN_PATH
    - STRAPI_ADMIN_BACKEND_URL
    - STRAPI_TELEMETRY_DISABLED
✔ Building build context (323ms)
✔ Creating admin (15750ms)
⠦ Loading Strapi[2024-01-07 03:00:05.219] info: The Users & Permissions plugin automatically generated a jwt secret and stored it in .env under the name JWT_SECRET.
✔ Loading Strapi (5234ms)
✔ Generating types (510ms)
✔ Cleaning dist dir (14ms)
✔ Compiling TS (4581ms)

 Project information

┌────────────────────┬──────────────────────────────────────────────────┐
│ Time               │ Sun Jan 07 2024 03:00:10 GMT+0000 (Coordinated … │
│ Launched in        │ 10332 ms                                         │
│ Environment        │ development                                      │
│ Process PID        │ 341                                              │
│ Version            │ 4.16.2 (node v20.10.0)                           │
│ Edition            │ Community                                        │
│ Database           │ postgres                                         │
└────────────────────┴──────────────────────────────────────────────────┘

 Actions available

One more thing...
Create your first administrator 💻 by going to the administration panel at:

┌─────────────────────────────┐
│ http://localhost:1337/admin │
└─────────────────────────────┘

 ブラウザからhttp://localhost:1337/adminにリクエストする。  上記のようにユーザ作成画面が表示されるので適当なユーザを作成しログインする。
 ブログ用のCMSを想定しているのでとりあえずArticleモデル(Collection Type)を作成してみる。  Collection Typeについては下記を参照。


 記事の内容用のcontentフィールドも追加した。  適当な記事を一つ作ってみる。

データベース確認

 データベースの方はどうなっているのか確認してみる。
 テーブル一覧は下記

strapi=# \dt
                           List of relations
 Schema |                     Name                      | Type  | Owner
--------+-----------------------------------------------+-------+-------
 public | admin_permissions                             | table | admin
 public | admin_permissions_role_links                  | table | admin
 public | admin_roles                                   | table | admin
 public | admin_users                                   | table | admin
 public | admin_users_roles_links                       | table | admin
 public | articles                                      | table | admin
 public | files                                         | table | admin
 public | files_folder_links                            | table | admin
 public | files_related_morphs                          | table | admin
 public | i18n_locale                                   | table | admin
 public | strapi_api_token_permissions                  | table | admin
 public | strapi_api_token_permissions_token_links      | table | admin
 public | strapi_api_tokens                             | table | admin
 public | strapi_core_store_settings                    | table | admin
 public | strapi_database_schema                        | table | admin
 public | strapi_migrations                             | table | admin
 public | strapi_release_actions                        | table | admin
 public | strapi_release_actions_release_links          | table | admin
 public | strapi_releases                               | table | admin
 public | strapi_transfer_token_permissions             | table | admin
 public | strapi_transfer_token_permissions_token_links | table | admin
 public | strapi_transfer_tokens                        | table | admin
 public | strapi_webhooks                               | table | admin
 public | up_permissions                                | table | admin
 public | up_permissions_role_links                     | table | admin
 public | up_roles                                      | table | admin
 public | up_users                                      | table | admin
 public | up_users_role_links                           | table | admin
 public | upload_folders                                | table | admin
 public | upload_folders_parent_links                   | table | admin
(30 rows)

 色々あるけどarticlesテーブルが先ほど作ったContent Typeだろう。
 テーブル定義はこちら

strapi=# \d articles
                                           Table "public.articles"
    Column     |              Type              | Collation | Nullable |               Default
---------------+--------------------------------+-----------+----------+--------------------------------------
 id            | integer                        |           | not null | nextval('articles_id_seq'::regclass)
 title         | character varying(255)         |           |          |
 created_at    | timestamp(6) without time zone |           |          |
 updated_at    | timestamp(6) without time zone |           |          |
 published_at  | timestamp(6) without time zone |           |          |
 created_by_id | integer                        |           |          |
 updated_by_id | integer                        |           |          |
 content       | text                           |           |          |
Indexes:
    "articles_pkey" PRIMARY KEY, btree (id)
    "articles_created_by_id_fk" btree (created_by_id)
    "articles_updated_by_id_fk" btree (updated_by_id)
Foreign-key constraints:
    "articles_created_by_id_fk" FOREIGN KEY (created_by_id) REFERENCES admin_users(id) ON DELETE SET NULL
    "articles_updated_by_id_fk" FOREIGN KEY (updated_by_id) REFERENCES admin_users(id) ON DELETE SET NULL

 created_atとかupdated_atはデフォルトで作成されるようだ。
 先ほど作成した記事も見てみる。

strapi=# select id, created_at, updated_at, published_at, title, content from articles;
 id |       created_at        |       updated_at        | published_at |   title    | content
----+-------------------------+-------------------------+--------------+------------+----------
  1 | 2024-01-20 05:52:39.336 | 2024-01-20 05:52:39.336 |              | test title | # hoge  +
    |                         |                         |              |            | hogehoge
(1 row)

 まだ下書きのままだからpublished_atは空だ。記事内容がそのままcontentカラムに格納されていることがわかる。

Herokuにデプロイ

 上記ページに従い作業すればデプロイできる。特に詰まるところはなかったかな。
 前はHeroku無料枠があったけど無くなっちゃったみたいだね。まあ、他のクラウドサービス使ってもどのみちお金はかかるし、Herokuへのデプロイドキュメントがしっかりと用意されているからデプロイ先の見直しはしなかった。ただ一番安いプランにした。  PostgresプラグインはMiniプラン。
  https://devcenter.heroku.com/articles/heroku-postgres-plans#essential-tier

 dynoはEcoプラン。
  https://devcenter.heroku.com/articles/eco-dyno-hours
 Ecoプランはしばらくアクセスがなかったらスリープしちゃうみたいだね。スリープした状態でアプリケーションにアクセスすると表示されるまでに少し時間かかる。

 このように本番環境ではContent Typeを更新することはできない。更新したいときは開発環境で起動したUIから操作し、本番環境に反映する必要がある。


 とりあえず今回はここまで。次何するかは決まっていない。タイトルはナンバリングしているけど(2)が本当に作成されるかも怪しい。まあ、ここで終わるとただHerokuにスパチャしているだけになるので何かしら使えるものを作っていきたいと思う。

【次回】 【自作ブログ】(2) - Strapi Rich text (Markdown)の改行の扱いがちょっと気になった - のうらリリースノート