🦁

Python×Next.js×PostgreSQLの環境をDockerで構築する

2024/01/01に公開

はじめに

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で構築しました。

GitHubで編集を提案

Discussion