はじめに
初めまして!
エンジニアになって数年、今まで本を読むだけでしたが、もっとプライベートで楽しみながら成長したい!自分が学んだ足跡を残していきたい!と思い記事を書きました!
最終的には自在に開発できるようになりたいと思っています。
いろいろな記事を参考にさせてもらっています。
その中でもこれってどういう意味?とかつまづいたところを念入りに書いていこうかと思います。
今回の目的
今までの総決算をします!
他、記事の中で紹介させていただきます!
使用したものや事前準備
・Macbook Pro
・ChatGPT
・Github Copilot
・Docker
・Visual Studio Code
・Next.js(React)
・CakePHP(PHP)
・PostgreSQL
・adminer
すぐに試したい方へ〜Github〜
パッケージ類を抜いていますが、今回作成した部分はあげております!
実際に使う方はclone後に下記を実施してください。
・webフォルダに移動し、コマンドの実施
npm install
npm run build
・api/srcフォルダに移動し、コマンドの実施
composer install
・api/src/config/app_local.phpのソースを修正(composer install実行後に表示されます)
'default' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Postgres',
'persistent' => false,
'host' => 'db',
'username' => 'app',
'password' => 'app',
'database' => 'app',
'encoding' => 'utf8',
'timezone' => 'UTC',
'cacheMetadata' => true,
],
・プロジェクトフォルド直下に移動し、コマンドの実施
docker compose up
・下記にアクセスして、Helloテーブル>Helloカラム(character 10)を作成し、データも作成
http://localhost:8080/?pgsql=db&server=db&username=app&db=app
パスワード:app
・http://localhost:80 にアクセス
※DBで失敗する場合は、 http://localhost:80/hello でアクセスしてエラー出てないか確認
全体構成
目指した機能
・フロントからAPI呼び出せる。
・APIからDBにアクセスしデータを取り出せる。
・APIからフロントにJSONデータを渡せる。
・返却されたJSONデータを画面に表示できる。
技術構成
フロント(静的ページ):Next.js
バック(動的ページ):CakePHP
DB:PostgreSQL
webサーバ:Nginx
アプリケーションWebサーバ:PHP-FPM
DB管理ツール:adminer
フォルダ構成
作成手順
各コンテナサービスごとに進めていきます。
apiフォルダ
apiフォルダを作成し、その配下にDockerfileを作成しましょう。
Dockerfile作成&ビルド
RUNコマンド
・パッケージリスト更新
・PHP拡張機能をコンパイルしてインストールするコマンド
・CakePHP5をインストールするために必要なintlとZIP関係、依存関係のインストール
・PostgreSQLデータベースに接続するための拡張機能、intlとZIPのインストール
COPYコマンド
・Dockerイメージのcomposerを使用
FROM php:8.3-fpm-bookworm
ENV TZ Asia/Tokyo
RUN apt-get update && \
apt-get install -y libonig-dev libicu-dev libpq-dev libzip-dev zip && \
docker-php-ext-install pdo_pgsql intl zip
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www
参考記事:
ターミナルを開いて、apiフォルダに移動しビルドします。
cd api
docker build -t api .
イメージができたか確認しましょう。
docker image ls
中を確認したい人はこちらもみましょう。戻りたい時はexitで戻ってこれます。
docker run -it --rm api:latest /bin/bash
一旦ここで、abi部分は止めておきます。
apiのプロジェクト作成は後半で行います。
dbフォルダ
compose.ymlを作成し、dbサービスとadminerサービスの設定情報を記述します。
compose.yml作成
compose.ymlの記述内容です。
※段階的に理解して進めていくために記述しており、後でまとめて再掲するのでいらない部分は消していただいて構いません。
version: '3'
services:
db:
image: postgres:12.18-bookworm
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
volumes:
- postgres:/var/lib/postgresql/data
#networks:
#- api-db-network
#- db-gui-network
adminer:
image: adminer
restart: always
ports:
- 8080:8080
#networks:
#- db-gui-network
depends_on:
- db
volumes:
postgres:
#networks:
# web-api-network:
# driver: bridge
# api-db-network:
# driver: bridge
# db-gui-network:
# driver: bridge
dbのvolumesに設定するパスについて
データの保存先は、使用しているLinuxディストリビューションやPostgreSQLのバージョン等に依存します。
Volumeが二つできた等、不具合が発生した場合は要確認しましょう。
/var/lib/postgresql/data
Debianベースのディストリビューション(Ubuntuを含む)でDebianやUbuntuのパッケージマネージャー(apt)を使用してPostgreSQLをインストールした場合、使用されることが多いです。
/var/lib/pgsql/data
Red Hatベースのディストリビューション(Fedora、CentOS、RHELなど)でよくみられます。
PGDATAの位置については下記を参照ください。
起動確認
compose.ymlのあるフォルダに移動してから、コマンドを実行しましょう。
起動後にログインできることを確認します。
docker compose up -d --build
データベース種類:PostgreSQL
サーバ:db
ユーザ名:app
パスワード:app
データベース:app
ログインできたら、試しにテーブルを作成したり、コンテナを再起動してみましょう。テーブルが残っていることが確認できるかと思います。
起動したコンテナは終了させます。
docker compose down
webフォルダ
next.jsをビルドしてデプロイするパターンを作成します。
開発環境用に、ホットリロードしながら開発するパターンはまたいずれ・・・。
ローカルマシンにNode.jsが入っていることが前提で進めていきます。
MacBookの方はこちらの記事を参考にして導入してください。
Node.jsのバージョンは20.9.0(LTS)を使用します。
webプロジェクトの作成
説明がわかりづらくて申し訳ないですが、、、
全体のプロジェクト直下で下記のコマンドを実行し、 Webプロジェクトを作成します。
設問が出てきます。project nameはwebにし、他の設定はデフォルト(下記の画像の通り)に進めましょう。
npx create-next-app@latest --typescript
少し待って、作成が完了したら実行できるか確認してみましょう。
cd web
npm run dev
ホットリロードで変更がかかるのも確認できると尚いいです。
web/app/page.tsxの18行目に自分の名前を入れてみて表示が変わるか確認してみてください。
ある程度堪能したらcontrol+c(ctrl+c)で終了します。
次の手順に進みましょう。
ビルド設定変更
Next.jsには静的部分を担当してもらうので、ビルド時にreactと同じように配置するだけで動く設定に変更します。
参考:
(動的な部分が必要な場合には、Nginx使う場合どう構成するのだろうとか、そもそもPHP以外に使うものなのかも整理できていないなとか色々思いましたが、とりあえず今回は置いておきます。。)
ちなみに今回の設定をしないと、Next.jsはreactのみの時と違い、ビルド後もサーバー上でnpm startが必要です。Vercelを使用する方法がよくみられたのでいずれ調べることができたらと思います。
webフォルダ直下の next.config.mjs を開いてください。
nextConfig に output: 'export' を追加して保存してください。
output: 'export'
ビルドした時に、outフォルダが作成されることを確認します。(今回のNginxサーバーに配置する資材です。)
webフォルダに遷移し、下記コマンドを実行してみてください。
npm run build
次にいきましょう。
nginxフォルダ
全体のプロジェクト直下にnginxフォルダを追加しましょう。
下記記事を参考にして設定します。
Nginxのバージョンはメジャーバージョン(mainline)の1.25.4を設定します。
Nginx関連ファイル修正
compose.yml
下記設定に変更しましょう。
・- ./web/out:/usr/share/nginx/htmlは、Web部分で行なったビルドファイルと紐づけています。
version: '3'
services:
nginx:
image: nginx:1.25.4-bookworm
ports:
- 80:80
volumes:
- ./web/out:/usr/share/nginx/html
- ./nginx/fastcgi_params:/etc/nginx/fastcgi_params
- ./nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf
# depends_on:
# - api
# networks:
# - web-api-network
fastcgi_params
nginxフォルダ直下にファイルを作成し、下記内容を追加しましょう。
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param HTTPS $https;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;
default.conf
nginx/conf.d直下にファイルを作成し下記内容を追加しましょう。
・location / {〜は、Next.jsを見にいくように作成しています。
・location /Hello {は、apiサービスが完成していないため、一時的に127.0.0.1へ変更しています。
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /Hello {
root /var/www/src/webroot/index.php;
fastcgi_param HTTP_PROXY "";
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
}
}
コンテナ起動確認
全体のプロジェクト直下で下記のコマンドを実行してください。
コンテナを起動し、webプロジェクトのビルドファイルで画面を表示できるか確認します。
cd ../
docker compose up -d --build
下記にアクセスしてNode.js画面が表示されたら成功です。
先ほどと同様に修正しても更新がかからないことも確認してみるといいでしょう。
起動したコンテナは終了させます。
docker compose down
NginxイメージにNode.jsを入れたいときは
Dockerfileを作成してこちらの設定を入れましょう。
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash && \
export NVM_DIR="$HOME/.nvm" && \
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" && \
nvm install 20.9.0
コンテナ起動準備
全体の確認が取れたので協調して動くようにプロジェクト作成と再度修正します。
CakePHPプロジェクト作成
compose.ymlにapiの設定を追加します。他は全てコメントアウトか削除してください。
version: '3'
services:
api:
build: ./api
volumes:
- ./api:/var/www/
# networks:
# - web-api-network
apiサービスのみ起動します。
compose.ymlがあるフォルダでコマンドを実行しましょう。
docker compose up -d --build
実行コマンドにsrcを追加し、compose.ymlがあるフォルダでコマンドを実行します。(srcフォルダは自動追加されます。)
※Dockerfileがある階層では作成するとフォルダがからじゃないと怒られてエラーが発生します
docker compose exec api bash -c 'composer create-project --prefer-dist cakephp/app:5 ./src'
パーミッションの設定を自動でやってくれるかですがデフォルトのまま(Y)で進めます。
フォルダが作成されました。
起動したコンテナは終了させます。
docker compose down
設定ファイル&ソースファイル修正
default.conf
パス:nginx/conf.d/default.conf
nginxサービスがapiサービスに繋げられるようにfastcgi_passのポート番号を api に変更します。
location /Hello {
root /var/www/src/webroot/index.php;
fastcgi_param HTTP_PROXY "";
fastcgi_pass api:9000;
include fastcgi_params;
}
app.php
接続設定情報は下記ページの設定項目を参考にしています。
'default' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Postgres',
'persistent' => false,
'host' => 'db',
'username' => 'app',
'password' => 'app',
'database' => 'app',
'encoding' => 'utf8',
'timezone' => 'UTC',
'cacheMetadata' => true,
],
検証ソースファイル&検証データ作成
api(CakePHP)
HelloController.php
api/src/src/Controller 配下に HelloController.php を作成します。
http://localhost:80/Hello でアクセスすると HelloController.php が対応してくれます。
アクセスしてきた時にテーブル取得結果のJSONデータを返却するように作成します。
<?php
namespace App\Controller;
use Cake\Controller\Controller;
use Cake\Http\Response;
use Cake\Datasource\ConnectionManager;
class HelloController extends Controller
{
public function index(): Response
{
$connection = ConnectionManager::get('default');
// SQLクエリを実行
$results = $connection->execute('SELECT "Hello" FROM "Hello"')->fetchAll('assoc');
$message = 'Hello';
$this->set(compact('results'));
$this->viewBuilder()->setOption('serialize', ['results']);
return $this->response->withType('application/json')->withStringBody(json_encode(compact('results')));
}
}
web(Next.js)
web/app/page.tsxを変更します。おしゃれなページで名残惜しいですがすっきり消しちゃいましょう!
コード補足
"use client"でクライアント処理であることを明示しています。これがないとuseEffectとuseStateが使えません。(Next.js 13以降からの仕組み)
ルーティングの仕組みは、app配下のpage.tsxがindex.html扱いになります。
他のページにアクセスさせたい場合は、app配下でフォルダを作成しpage.tsxを配置すればルーティングできます。
(例:/testでアクセスした時はtest/page.tsxが呼ばれる。)
Nginx経由でやる方法が地味に理解するのが難しかったです。
今後改善していきたいと思います。
page.tsx
"use client";
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const HelloWorld: React.FC = () => {
const [data, setData] = useState('');
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get('http://localhost:80/Hello');
console.log("ok");
setData(response.data);
} catch (error) {
console.error('Error fetching data:', error);
}
};
fetchData();
}, []);
return (
<div>
<h1>Hello, World!</h1>
<p>{JSON.stringify(data)}</p>
</div>
);
};
export default HelloWorld;
axiosインストール
不足しているパッケージをインストールします。
cd web
npm install axios
db(PostgreSQL+adminer)
テーブルとデータを作成します。
compose.yml修正
compose.ymlをdbサービスとadminerサービスのみにします
version: '3'
services:
db:
image: postgres:12.18-bookworm
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
volumes:
- postgres:/var/lib/postgresql/data
networks:
# - api-db-network
- db-gui-network
adminer:
image: adminer
restart: always
ports:
- 8080:8080
networks:
- db-gui-network
depends_on:
- db
volumes:
postgres:
networks:
# web-api-network:
# driver: bridge
# api-db-network:
# driver: bridge
db-gui-network:
driver: bridge
サービス起動 & adminer接続
compose.ymlが配置されているフォルダでコマンドを実行します。
cd ../
docker compose up -d --build
adminerに接続します。
ユーザ名:app
パスワード:app
データベース:app
テーブル作成
左のメニューの「テーブル作成」を押下します。
HelloテーブルにHelloカラムを追加し「保存」を押下します。
テーブル名:Hello
列名:Hello
型:character
長さ:10
データ作成
作成後、左画面から「テーブル名(Hello)」を押下します。
「項目の作成」を押下します。データの内容はなんでも構わないので入力後、「保存」を押下します。
(例:DBから出てきました)
SQL確認
左に表示されている「SQLコマンド」を押下します。
下記のSQLをコピペし、「実行」を押下します。
SELECT "Hello" FROM "Hello"
データが表示されればOKです。
起動したコンテナは終了させます。
docker compose down
compose.yml完成
全ての確認が取れたので、compose.ymlを完成させます。
コピーして貼り付けましょう。
version: '3'
services:
nginx:
image: nginx:1.25.4-bookworm
ports:
- 80:80
volumes:
- ./web/out:/usr/share/nginx/html
- ./nginx/fastcgi_params:/etc/nginx/fastcgi_params
- ./nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- api
networks:
- web-api-network
api:
build: ./api
volumes:
- ./api:/var/www/
networks:
- web-api-network
- api-db-network
db:
image: postgres:12.18-bookworm
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
volumes:
- postgres:/var/lib/postgresql/data
networks:
- api-db-network
- db-gui-network
adminer:
image: adminer
restart: always
ports:
- 8080:8080
networks:
- db-gui-network
depends_on:
- db
volumes:
postgres:
networks:
web-api-network:
driver: bridge
api-db-network:
driver: bridge
db-gui-network:
driver: bridge
下記、サービスごとの補足説明です。
追加で設定しているのは2点です。
depends_on : apiサービスの後に起動を開始するようにしています
networks : web-api-networkを設定しているサービスとのみ通信するようにしています。
nginxサービス
ここに記載はありませんが、adminerとポート番号が被らないように注意しましょう。
depends_on:
- api
networks:
- web-api-network
apiサービス
networks:
- web-api-network
dbサービス
networks:
- api-db-network
- db-gui-network
adiminer
networks:
- db-gui-network
depends_on:
- db
その他
ネットワークの依存関係を追加しています。
networks:
web-api-network:
driver: bridge
api-db-network:
driver: bridge
db-gui-network:
driver: bridge
コンテナ起動&動作確認
だいぶ長くなりました。お疲れ様でした。
ここで全体の稼働確認をします。
nginx/conf.d/default.conf
127.0.0.1のままになっていないか念のため確認しましょう。変更していなかった場合は api に変更します。
コンテナ起動
webプロジェクトのビルドを行います。
cd web
npm run build
ビルドが完了したら起動しましょう。
cd ../
docker compose up -d --build
Docker Desktopがある方はコンテナが全て稼働しているか確認しやすいです。
アクセスして、結果が表示されれば成功です。
最後に
なるべく分解して取り組むことにとても悩みました。
入り組んでくるとややこしくなり把握できなくなるため、段階踏んでやりたかったですがなかなかできず気づいたら鳥の声が聞こえていました。
後半に至っては知らないことばかりで、ChatGPTとレスバトルを繰り広げるなど、AIいてくれてよかったと思います。一人ではとてもできなかったです!
開発環境をホットリロードで実施できたらなと思うので引き続きやっていきたいと思います!
おまけ
開発環境整備してみた