Python×Next.js×PostgreSQLの環境をDockerで構築する
はじめに
langchainを使った個人開発をするためにPython×Next.js×PostgreSQLの環境をDockerで構築したので、備忘録として残しておきます。
各環境をDockerfileで定義して、docker-composeで管理するようにしています。
完成イメージ
ディレクトリ構成
.
├── front
├── services
│ ├── ai
│ │ ├── api
│ │ └── db
└── docker-compose.yml
front, ai_service(api), ai_dbの3つのコンテナを作成し、これらをdocker-composeで管理しています。
手順
docker-compose.ymlの作成
まず最初にdocker-compose.ymlを作成してしまいます。
version: '3.8'
services:
front:
build:
context: ./front
dockerfile: Dockerfile
ports:
- "3000:3000"
depends_on:
- ai_service
volumes:
- ./front:/app
ai_service:
build:
context: ./services/ai/api
ports:
- "5000:5000"
depends_on:
- ai_db
ai_db:
image: postgres:15
volumes:
- ./services/ai/db/init:/docker-entrypoint-initdb.d
- ./services/ai/db/data:/var/lib/postgresql/data
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: passw0rd
ports:
- "5432:5432"
db -> api -> frontの順番でコンテナを起動するようにしています。
db
まず最初にdbを作成します。dbではDockerfileを使わず、docker-compose.ymlで定義しています。
この部分です。
ai_db:
image: postgres:15
volumes:
- ./services/ai/db/init:/docker-entrypoint-initdb.d
- ./services/ai/db/data:/var/lib/postgresql/data
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: passw0rd
ports:
- "5432:5432"
init
コンテナを起動すると、/docker-entrypoint-initdb.d
にあるSQLファイルが実行されます。今回は、init
ディレクトリにinit.sql
を作成し、テーブルを作成するSQLを記述しています。
DROP TABLE IF EXISTS messages;
CREATE TABLE IF NOT EXISTS messages(
id serial PRIMARY KEY,
title text NOT NULL,
body text NOT NULL
);
INSERT INTO messages (title, body) VALUES ('Initial Message', 'hello from python');
これで、コンテナを起動すると、以下のようなテーブルが作成されます。
$ docker compose exec ai_db psql -U postgres -d postgres -c "\dt"
List of relations
Schema | Name | Type | Owner
--------+---------+-------+--------
public | messages | table | postgres
(1 row)
$ docker compose exec ai_db psql -U postgres -d postgres -c "select * from messages"
id | title | body
----+------------------+-------------------
1 | Initial Message | hello from python
(1 row)
データの永続化
開発中にコンテナを再起動すると、データが消えてしまうので、データの永続化を行っています。
ai_db:
image: postgres:15
volumes:
- ./services/ai/db/init:/docker-entrypoint-initdb.d
- ./services/ai/db/data:/var/lib/postgresql/data <<< ここ
/services/ai/db/data:/var/lib/postgresql/dataにデータが保存されます。
api
次にapiを作成します。ディレクトリ構成は以下のようになります。
.
├── Dockerfile
├── app.py
├── health
│ ├── __init__.py
│ └── health.py
└── requirements.txt
health.pyはapiの動作確認用に作成したエンドポイントで、app.pyから呼び出されます。
まず以下のようにDockerfileを作成します。
FROM python:3.10
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
EXPOSE 5000
CMD ["python", "app.py"]
requirements.txt
apiで使用するライブラリを記述します。
Flask
flask-cors
psycopg2-binary
- Flask: Webフレームワーク
- flask-cors: CORS対応
- psycopg2-binary: PostgreSQLを使うためのライブラリ
app.py
apiのエントリーポイントはapp.pyです。
from flask import Flask
from flask_cors import CORS
from health.health import health_bp
app = Flask(__name__)
CORS(app)
app.register_blueprint(health_bp, url_prefix='/api')
if __name__ == '__main__':
app.run(debug=False, host='0.0.0.0', port=5000)
health
apiの動作確認用にhealthエンドポイントを作成します。動作確認用にhealth
を呼び出します。
また、frontからapiにアクセスするために、CORS対応をしています。
from flask import Blueprint, jsonify
import psycopg2
import os
# 環境変数からデータベース接続情報を取得(適宜変更してください)
DATABASE_URL = os.getenv('DATABASE_URL', 'postgresql://postgres:passw0rd@ai_db:5432/postgres')
conn = psycopg2.connect(DATABASE_URL)
cur = conn.cursor()
health_bp = Blueprint('health', __name__)
@health_bp.route('/health', methods=['GET'])
def health_check():
return jsonify({"status": "healthy"}), 200
@health_bp.route('/greeting', methods=['GET'])
def greeting():
conn = None
try:
cur.execute('SELECT body FROM messages ORDER BY id LIMIT 1')
body = cur.fetchone()
if body:
return jsonify({"message": body[0]}), 200
else:
return jsonify({"message": "No message found"}), 404
except (Exception, psycopg2.DatabaseError) as error:
return jsonify({"error": str(error)}), 500
finally:
if conn is not None:
conn.close()
先ほど作成したdbコンテナとコネクションを張り、messages
テーブルからデータを取得しています。
front
最後にfrontを作成します。ディレクトリ構成は以下のようになります。
仮のDockerfileを作成しておきます。
FROM node:21-alpine
WORKDIR /app/
このコンテナに入って、npx create-next-app
を実行します。コンテナに入ったらDockerfileを一旦削除する必要があります。
$ docker compose run --rm front sh
/app # rm Dockerfile
/app # npx create-next-app .
コンテナを抜けて、Dockerfileを再度作成します。
FROM node:21-alpine
WORKDIR /app/
COPY . .
RUN npm install
EXPOSE 3000
CMD ["npm", "run", "dev"]
また、page.tsxを以下のように編集します。
export default async function Home() {
const data = await fetch('http://ai_service:5000/api/greeting', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
});
const greeting = await data.json()
return (
<div>
<h1>{greeting.message}</h1>
<p>nextjsで描画しています</p>
</div>
)
}
fetch
のURLをhttp://ai_service:5000/api/greeting
にしています。ai_service
はdocker-compose.ymlで定義したコンテナ名です。
起動
$ docker compose up -d
localhost:3000
にアクセスして、hello from python
が表示されればOKです。
終了するときは以下のコマンドを実行します。
$ docker compose down
また、再ビルドするときは以下のコマンドを実行します。
$ docker compose up -d --build
おわりに
今回は、Python×Next.js×PostgreSQLの環境をDockerで構築しました。
Discussion