Strapi を開発環境と本番環境のDockerで動かしたい【その3】

【その1】【その2】の続きです。

前回までで、Strapi 本体の開発環境と本番環境の Dockerfile を用意することができました。

今回は、Strapi に接続するデータベースについて考えていきます。

Strapi とデータベース

Strapi は、ヘッドレスCMS(Contents Management System)です。コンテンツを管理します。
そのコンテンツはデータベースに保存されることになります。

データベース選び

SQLite

Strapi はデフォルトではSQLiteが使われています。
これは開発目的であり、SQLite – Strapi Developer Docsにも、ローカルで素早く起動するためにオススメと書かれています。

Docker で構築する場合には、コンテナ内にデータベースの中身を持ち、コンテナを破棄するとデータが消えてしまいます。Strapi + Docker では少し相性が悪そうです。

MySQL

MySQLはオープンソースのリレーショナルデータベース管理システムです。

わかりやすく直感的で、MySQL はデータベースの読み取り操作で高いパフォーマンスを発揮するように設計されているそうです。

なんとなく DB といえば MySQL という印象です。WordPress では MySQL を利用しているので馴染み深いです。

MariaDB

MariaDBはMySQLから派生したリレーショナルデータベース管理システムです。

MySQLとの互換性を保ちつつ、性能/堅牢性を高めるための独自機能を備えているそうです。

PostgreSQL

PostgreSQLはオープンソースのリレーショナルデータベース管理システムです。

PostgreSQL は MySQLより厳格で、書き込みの多い操作や同時読み取り/書き込みの操作に適しているそうです。

PostgreSQLはより厳密に標準に準拠しているため、PostgreSQL用に書いたコードは他のSQLバージョンに移植しやすいメリットがあるそうです。いいですね。

MySQLとMariaDBとPostgreSQLの選択

それぞれの違いについて調べてみました。

総合的なパフォーマンス

データベースが読み込みと書き込みのどちらの操作に対応するか、あるいは最適化する必要があるかを検討することが重要です。MySQLはデータベースの読み取り操作で高いパフォーマンスを発揮するように設計されていますが、PostgreSQLは書き込みの多い操作や同時読み取り/書き込みの操作に適しています。

PostgreSQLは高いスケーラビリティを持ち、高度なクエリをサポートしていることから、企業向けソリューションで高い評価を得ています。PostgreSQLは、データベースの更新を並列化し、ビジネスに対応した顧客にアピールするための工業的に強力な追加機能を提供します。PostgreSQLは、書き込みと同時の読み書き操作に最適化されています。構文の習得はより困難ですが、高度なクエリ機能を備えています。PostgreSQLはより厳密に標準に準拠しているため、PostgreSQL用に書いたコードは他のSQLバージョンに移植しやすくなっています。

PostgreSQL の厳格さには惹かれるものがあります。性能面で実感することがあるかは微妙なので、今後移植する可能性と好みで選択しようと思います。

前回の記事までは MySQL を選択していましたが、PostgreSQL へ乗り換えてみようと思います。

様々なサービス

Strapi のDeployment – Strapi Developer Docsに様々なサービスへのデプロイ方法が書かれています。

Strapi はそれぞれのサービス上のデータベースと接続して利用することになります。

AWS

AWSへのデプロイではAmazon RDSとの紐付けを行なっています。ドキュメントのサンプルではPostgreSQLが選択されています。

Azure

AzureへのデプロイではAzure Database for PostgreSQLAzure Database for MariaDB等への紐付けを行なっています。ドキュメントでのサンプルではMariaDBが選択されています。

DigitalOcean

DigitalOcean App PlatformへのデプロイでもDigitalOceanの管理画面上でデータベースを作成します。ドキュメントでのサンプルではPostgreSQLが選択されています。同様にDigitalOcean DropletsへのデプロイPostgreSQLが選択されています。

Google App Engine

Google App EngineへのデプロイではCloud SQL databaseを利用しています。これはPostgreSQLで構築されます。

Heroku

HerokuへのデプロイではHeroku Postgresを利用しています。

今回は、特にこういったサービスは利用せずにVPS上のUbuntuに直接構築していくことになります。
利用されているデータベースもPostgreSQLが多いですね。

PostgreSQLに移行する

前回の記事MySQLで構築されている Strapi をPostgreSQLに移行していきます。

STEP.1
ライブラリを追加する
# 作業ディレクトリに移動する
$ cd ~/StrapiProjects/donuts-strapi

# postgreSQLを追加する
$ yarn add pg
STEP.2
MySQLを削除する

package.jsonからMySQLを削除します

package.json
{
...
  "dependencies": {
    "@strapi/plugin-i18n": "4.3.8",
    "@strapi/plugin-users-permissions": "4.3.8",
    "@strapi/strapi": "4.3.8",
    "pg": "^8.8.0"
  },
...
}
STEP.3
server.ts を書き換える

Strapi のサーバ設定が行われているファイルの編集をします。./config/database.tspostgresに変更して、ポート番号を変えます。

./config/database.ts
export default ({ env }) => ({
  connection: {
    client: 'postgres',
    connection: {
      host: env('DATABASE_HOST', '127.0.0.1'),
      port: env.int('DATABASE_PORT', 5432),
      database: env('DATABASE_NAME', 'strapi'),
      user: env('DATABASE_USERNAME', 'strapi'),
      password: env('DATABASE_PASSWORD', 'strapi'),
    },
    debug: false,
  },
});

上記で用いられる環境変数も.envにて変更しておきます。

.env
DATABASE_HOST=donuts-strapi-postgres
DATABASE_PORT=5432
DATABASE_NAME=donuts-strapi
DATABASE_USERNAME=donuts-strapi
DATABASE_PASSWORD=donuts-strapi
SSLのエラー

./config/database.tsが以下の場合にはSSLをサポートしていないというエラーが表示されました。

# 開発環境でStrapiを起動する
$ docker run -p 1337:1337 --net=donuts-strapi-network --name donuts-strapi donuts-strapi-image

yarn run v1.22.19
$ strapi develop
Starting the compilation for TypeScript files in /opt/app
[2022-09-22 21:49:04.133] debug: ⛔️ Server wasn't able to start properly.
[2022-09-22 21:49:04.135] error: The server does not support SSL connections
Error: The server does not support SSL connections
./config/database.ts
export default ({ env }) => ({
  connection: {
    client: 'postgres',
    connection: {
      host: env('DATABASE_HOST', '127.0.0.1'),
      port: env.int('DATABASE_PORT', 5432),
      database: env('DATABASE_NAME', 'strapi'),
      user: env('DATABASE_USERNAME', 'strapi'),
      password: env('DATABASE_PASSWORD', 'strapi'),
      ssl: {
        rejectUnauthorized: env.bool('DATABASE_SSL_SELF', false), // For self-signed certificates
      },
    },
    debug: false,
  },
});

STEP.4
コンテナを起動する

PostgreSQLDocker イメージを用いて、コンテナを起動します。

# 作業ディレクトリに移動する
$ cd ~/StrapiProjects/donuts-strapi

# ネットワークを作成する
$ docker network create donuts-strapi-network

# ネットワークが作成されたことを確認する
$ docker network ls

NETWORK ID     NAME                            DRIVER    SCOPE
2bb3e3e77243   donuts-strapi-network           bridge    local

# PostgreSQLのコンテナを起動する
# docker run --name コンテナ名 -dit --net=ネットワーク名 -p ポートの指定 環境の設定
$ docker run --name donuts-strapi-postgres -dit --net=donuts-strapi-network -p 5432:5432 -e POSTGRES_DB=donuts-strapi -e POSTGRES_USER=donuts-strapi -e POSTGRES_PASSWORD=donuts-strapi postgres

# コンテナを確認する
$ docker container ls

CONTAINER ID   IMAGE                         COMMAND                  CREATED          STATUS          PORTS                                         NAMES
b11563ca8bf3   postgres                      "docker-entrypoint.s…"   45 seconds ago   Up 44 seconds   0.0.0.0:5432->5432/tcp                        donuts-strapi-postgres

# 開発環境のイメージを作成する
$ docker build -f dev.Dockerfile -t donuts-strapi-image .

# 開発環境でStrapiを起動する
$ docker run -p 1337:1337 --net=donuts-strapi-network --name donuts-strapi donuts-strapi-image

## 起動を確認

同様にステージング環境用の Dockerfile で起動することも確認します。

# ステージング環境のイメージを作成する
$ docker build -f stg.Dockerfile -t donuts-strapi-image-staging .

# ステージング環境でStrapiを起動する
$ docker run -p 1337:1337 --net=donuts-strapi-network --name donuts-strapi-staging donuts-strapi-image-staging

どちらも起動することが確認できました。

ボリュームをマウントする

このままでは PostgreSQL のコンテナを削除した場合に、データが消えてしまいます。
ボリュームを外部ファイルとリンクさせるためにvolumesを設定します。

docker runをするときに-v 実際の記憶領域パス:コンテナの記憶領域パスで設定します。

コンテナ側のパスはPostgreSQLのDockerイメージの説明によると/var/lib/postgresql/dataがデフォルトで指定されています。

This optional variable can be used to define another location – like a subdirectory – for the database files. The default is /var/lib/postgresql/data. If the data volume you’re using is a filesystem mountpoint (like with GCE persistent disks) or remote folder that cannot be chowned to the postgres user (like some NFS mounts), Postgres initdb recommends a subdirectory be created to contain the data.

# 作業ディレクトリに移動する
$ cd ~/StrapiProjects/donuts-strapi

# 先ほど作ったコンテナを削除しておく
$ docker container rm -f donuts-strapi-postgres

# PostgreSQLのコンテナを起動する
# docker run --name コンテナ名 -dit --net=ネットワーク名 -p ポートの指定 -v 実際の記憶領域パス:コンテナの記憶領域パス 環境の設定
$ docker run --name donuts-strapi-postgres -dit --net=donuts-strapi-network -p 5432:5432 -v data:/var/lib/postgresql/data -e POSTGRES_DB=donuts-strapi -e POSTGRES_USER=donuts-strapi -e POSTGRES_PASSWORD=donuts-strapi postgres

Changing SQLite to Postgres / MySQLを参考にしました。

また、.dockerignoredataが書かれていることも確認します。

最後に Strapi のコンテナを再構築します。

# コンテナを停止する
$ docker container rm -f donuts-strapi-staging

# あたらしくコンテナを起動する
$ docker run -p 1337:1337 --net=donuts-strapi-network --name donuts-strapi-staging donuts-strapi-image-staging
データが永続化されているか確認する

正しくマウントされているかを確認しましょう。

http://localhost:1337/adminにアクセスして、Userを追加してみます。

Content Manager > User > 右上のCreate new entryを選択

新しいユーザを追加してSAVEします。

Content Manager > Userで追加されたことを確認します。

この状態でコンテナを止めて再開してみましょう。

# 作業ディレクトリに移動する
$ cd ~/StrapiProjects/donuts-strapi

# Strapi のコンテナを止めて削除する
$ docker container rm -f donuts-strapi-staging

# http://localhost:1337/にアクセスできないことを確認

# 再度実行する
$ docker run -p 1337:1337 -d --net=donuts-strapi-network --name donuts-strapi-staging donuts-strapi-image-staging

http://localhost:1337/adminにアクセスして User を確認しましょう。

先ほどと同じで消えていません。

次に、PostgreSQLのコンテナも削除してしまいましょう。

# 作業ディレクトリに移動する
$ cd ~/StrapiProjects/donuts-strapi

# Strapi のコンテナを止めて削除する
$ docker container rm -f donuts-strapi-staging

# http://localhost:1337/にアクセスできないことを確認

# Postgres のコンテナを止めて削除する
$ docker container rm -f donuts-strapi-postgres

# ネットワークも削除しちゃう
$ docker network rm donuts-strapi-network-staging

# ネットワークから作り直し
$ docker network create donuts-strapi-volume-confirm

# Postgres のコンテナを起動
$ docker run --name donuts-strapi-postgres -dit --net=donuts-strapi-volume-confirm -p 5432:5432 -v data:/var/lib/postgresql/data -e POSTGRES_DB=donuts-strapi -e POSTGRES_USER=donuts-strapi -e POSTGRES_PASSWORD=donuts-strapi postgres

# Strapi のコンテナを起動
$ docker run -p 1337:1337 --net=donuts-strapi-volume-confirm --name donuts-strapi-staging donuts-strapi-image-staging

http://localhost:1337/adminにアクセスして User を確認して、データが残っていることを確認できました😊

メディアの保存先

最後にメディアの保存先を考えます。

Media Library > Add new assetsより画像を追加してみます。

この画像が配置された場所をコンテナの中に入って確認してみます。

# 作業ディレクトリに移動する
$ cd ~/StrapiProjects/donuts-strapi

# コンテナの中を確認してみる
$ docker container exec -it donuts-strapi-staging sh
% ls
build  config  database  favicon.ico  node_modules  package.json  public  src  tsconfig.tsbuildinfo
% cd public
% ls
robots.txt  uploads
% cd uploads
% ls
large_strapi_95516f0cf4.png  medium_strapi_95516f0cf4.png  small_strapi_95516f0cf4.png	strapi_95516f0cf4.png  thumbnail_strapi_95516f0cf4.png

% exit;

Strapi のコンテナを止めて、再度起動してみましょう。

# Strapi のコンテナを止めて削除する
$ docker container rm -f donuts-strapi-staging

# http://localhost:1337/にアクセスできないことを確認

# 再度実行する
$ docker run -p 1337:1337 -d --net=donuts-strapi-network --name donuts-strapi-staging donuts-strapi-image-staging

Media Library > Add new assetsを確認してみると、画像が破損してしまっているのがわかります。

データベース側には参照が残っていますが、画像本体はpublicフォルダの中に入っていて、publicフォルダはコンテナを削除したときに消えてしまっています。

なのでpublicディレクトリも外付けのボリュームにしていきます。

STEP.1
Dockerfileを編集する

dev.Dockerfileの方は特に編集しません。

stg.Dockerfileprod.DockerfileCOPY --from=builder /opt/app/public ./publicを削除します。

stg.Dockerfile
### deps ステージ ###
FROM node:16 AS deps
RUN apt-get update && apt-get install libvips-dev -y
ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}
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

ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}

WORKDIR /opt/app

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

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

WORKDIR /opt/app

ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}

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

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

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

EXPOSE 1337

ENV PORT 1337

CMD ["yarn", "start"]
STEP.2
Docker コンテナを起動する

Docker イメージを再作成後、-v public:/opt/app/publicを追加してコンテナを起動します。

# 作業ディレクトリに移動する
$ cd ~/StrapiProjects/donuts-strapi

# ステージング環境のイメージを作成する
$ docker build -f stg.Dockerfile -t donuts-strapi-image-staging .

# ボリュームを指定して起動する
$ docker run -p 1337:1337 -d --net=donuts-strapi-network -v public:/opt/app/public --name donuts-strapi-staging donuts-strapi-image-staging
STEP.3
ファイルの永続化を確認

再度別のファイルをアップロードして同様の手順で試します。

コンテナを止めて再度起動します。

# Strapi のコンテナを止めて削除する
$ docker container rm -f donuts-strapi-staging

# http://localhost:1337/にアクセスできないことを確認

# 再度実行する
$ docker run -p 1337:1337 -d --net=donuts-strapi-network -v public:/opt/app/public --name donuts-strapi-staging donuts-strapi-image-staging

コンテナを作り直しても画像が正しく表示されることを確認します。

おわりに

Strapi で管理しているコンテンツは、PostgreSQLのデータであるdataディレクトリと、アップロードしたメディアが格納されるpublicディレクトリの2つであることがわかりました。

これらのファイルをバックアップできれば、リストアもできそうです。

次回は、GitHub Actions を用いて 本番環境の VPS へのデプロイを目指します。