前回から1月以上の経過しましたが、Pythonの学習18日目です。
HTML変換サービスの開発も一段落したので、今日からは新しいWebサービスを作っていくことにします。
何を作るか?
新しくWebサービスを作成するにあたり、現状自分がほしいサービスを考えてみます。
色々とあるのですが、とりあえず一番必要性を感じるサービスとしては、「日々の作業時間を記録してあとから簡単に見直すことが出来るサービス」がほしいです。
これで、客観的に自分の作業や勉強内容を振返り、普段どれだけサボっていたかを見て反省できると良いなと思います…
また、このサービスを作成することで、以下の処理について勉強が出来ることも期待しています。
- データベースとの連携
- ログイン、ログアウト機能
- 機能ごとにプログラムを分けての開発(モジュール分割)
- Podmanを使ってコンテナ(仮想)化
VSCodeを使って開発していきます
今回から基本的にローカルの開発環境でのプログラムコードや設定ファイル等の作成・編集はVSCodeを使用して作業を行っていきます。
GitHubとの連携についても、VSCodeのGUI使用してコミットやプッシュ等を行ってます。
Webアプリの基本設計
最初から機能を盛り込みすぎると、たぶん完成させることができないと思うので、最低限必要と思う機能を実装することにします。
機能要件
まずは、最低限欲しい機能を考えてみます。
- ユーザ管理
- ユーザの作成と削除
- ログイン・ログアウト(ログインはメールアドレスとパスワードを使用)
- ユーザ名は任意で設定可能(あとから変更も可)
- 記録機能
- 活動内容(カテゴリ/タスク名): 何をしたか(例: "Python学習", "ブログ記事作成")
- 使用した教材や資料などを記録: 参考書や書籍名等
- 開始時刻: 作業を始めた日時
- 終了時刻: 作業を終えた日時
- 合計時間: 費やした時間の長さ
- メモ/備考: その他、特記事項
- レポートの表示
- 日、週、月、年ごとのレポート表示
開発環境
これまで学んできた、Python + uv + Flask + Gunicorn + Nginx」といった構成に、データベースであるPostgreSQLを追加する形になります。
また、今回はPodmanも導入して、コンテナ化による仮想環境でWebアプリを開発していくことにしました。
- 言語:Python
- フレームワーク:Flask
- モジュール管理: uv
- WSGIサーバ:Gunicorn
- Webサーバ:Nginx
- データベース:PostgreSQL
- コンテナ管理:Podman
使用する予定のPythonモジュール
- python-dotenv:.envファイルに書いたパスワードなどの情報をプログラムから読み込めるようにする
- Flask-SQLAlchemy:SQLを書かずに、Pythonのコードでデータベースを操作するための便利なツール(ORM)
- Flask-Migrate:データベースの構造を変更(マイグレーション)するためのツール
- psycopg2-binary:PythonとPostgreSQLをつなぐためのドライバ
- Flask-Login:「ログイン状態の維持」や「ログアウト」、「ログインしていない人は見れないページの設定」などを超簡単に実装できるライブラリ
- Flask-WTF:フォーム(入力画面)を作るためのライブラリで、CSRF対策(Webサイトへの攻撃を防ぐ機能)もついてくる
- email-validator:Flask-WTF でメールアドレスの入力チェックをする時に使われるライブラリ
コンテナ管理
Podmanで以下のサーバをコンテナとして動作させます。
- APPサーバ:Python + Gunicorn
- Webサーバ:Nginx
- DBサーバ: PostgreSQL
環境構築
開発を行うための環境を構築していきます。
GitHubにリポジトリを作成しローカルにclone
まず初めにGitHubにリポジトリを作成します。
作成されたリポジトリを、「git clone」でローカルにリポジトリをダウンロードしてきます。
$ git clone git@github.com:servermemo/time_report.git
作成されたディレクトリに移動して、これから使用する予定のディレクトリを最初に作成しておきます。
$ cd time_report $ mkdir app $ mkdir -p app/auth $ mkdir -p app/tracker $ mkdir -p app/templates/auth $ mkdir -p app/templates/tracker $ mkdir -p app/static/css $ mkdir -p app/static/js $ mkdir -p docker/python $ mkdir -p docker/nginx/conf.d
uvの初期化
今回もPythonのライブラリ管理にuvを採用しました。
uvはすでにインストール済みなので、uvの初期化を行います。
$ uv init Initialized project `time-report`
初期化が完了すると、pyproject.tomlなどのファイルが作成されます。
pyproject.tomlの編集
前回は「requirements.txt」を使用してライブラリの管理を行っていましたが、今回は「pyproject.toml」を使用します。
これは、「PEP 518」というルールで決められた、Pythonの新しい公式標準フォーマットです。
pyproject.tomlを使うことのメリット
- 情報の一元管理: 「プロジェクト名」「依存ライブラリ」「ツールの設定」を、この1つのファイルにまとめられる。
- 構成が柔軟: 本番用と開発用(dev)をグループ分けして綺麗に管理できる。
- ビルドシステムの定義: 将来ライブラリとして配布する際の設定も含めることができる。
「pyproject.toml」は「uv init」で作成されているので、それを以下のように編集します。
[project]
name = "time-report"
version = "0.1.0"
description = "A simple time tracking app with Flask"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"flask",
"flask-sqlalchemy",
"flask-migrate",
"flask-login",
"flask-wtf",
"email-validator",
"psycopg2-binary",
"python-dotenv",
"gunicorn",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["app"]
設定内容のポイント
「pyproject.toml」の中で、ちょっと分かりづらい部分について説明します。
[build-system]ブロック
uvが標準採用しているビルドツール「Hatchling」を使う宣言をしています。
requires = ["hatchling"]
このアプリをインストールやビルドする際には、「Hatchling(ハッチリング)」が必要という宣言です。
build-backend = "hatchling.build"
Hatchlingの中にある、buildという機能を呼び出して使用するようにという指示です。
[tool.hatch.build.targets.wheel] ブロック
プロジェクト名(time-report)と実際のディレクトリ名(app)が異なるため、プログラム本体の格納場所を明示的に指定しています。
packages = ["app"]
プログラム本体が「app」ディレクトリに格納されているので、そこを参照するようにという指示です。
今回は、プロジェクトの名前を「name = "time-report"」と設定したのですが、プロジェクトの中には「time-report」ディレクトリ存在しないので、そのままだとビルドする際にディレクトリを見つけることができずにエラーが発生してしまいます。
ですので、「packages = ["app"]」と設定することで、「app」ディレクトリを参照するように設定しています。
ライブラリのインストールとuv.lockの作成
「uv sync」を実行することで、「pyproject.toml」の内容に合わせてライブラリをインストールし、同時に「uv.lock」ファイルを作成してくれます。
「uv.lock」があることで、ライブラリのバージョンを固定できるため、「勝手にバージョンが上がったりしてプログラムが動作しなくなる」といったトラブルを防ぐことができます。
$ uv sync
Resolved 26 packages in 1ms
Built time-report @ file:///home/tamohiko/project/time_report
Prepared 7 packages in 1.79s
Installed 25 packages in 36ms
+ alembic==1.17.2
+ blinker==1.9.0
+ click==8.3.1
+ dnspython==2.8.0
+ email-validator==2.3.0
+ flask==3.1.2
+ flask-login==0.6.3
+ flask-migrate==4.1.0
+ flask-sqlalchemy==3.1.1
+ flask-wtf==1.2.2
+ greenlet==3.2.4
+ gunicorn==23.0.0
+ idna==3.11
+ itsdangerous==2.2.0
+ jinja2==3.1.6
+ mako==1.3.10
+ markupsafe==3.0.3
+ packaging==25.0
+ psycopg2-binary==2.9.11
+ python-dotenv==1.2.1
+ sqlalchemy==2.0.44
+ time-report==0.1.0 (from file:///home/tamohiko/project/time_report)
+ typing-extensions==4.15.0
+ werkzeug==3.1.4
+ wtforms==3.2.1
Podman環境の構築
インストール
「apt」を使って「Podman」と「podman-compose」をインストールします。
$ sudo apt update $ sudo apt install -y podman podman-compose
docker-compose.ymlを作成
プロジェクトのディレクトリに、複数のコンテナを管理するための「docker-compose.yml」を作成します。
version: '3.8'
services:
# --- 1. データベース (PostgreSQL) ---
db:
image: docker.io/library/postgres:15-alpine
restart: always
environment:
POSTGRES_USER: ${DB_USER} # .envから読み込み
POSTGRES_PASSWORD: ${DB_PASSWORD} # .envから読み込み
POSTGRES_DB: ${DB_NAME} # .envから読み込み
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432" # ホストのGUIツール(DBeaverなど)から接続したい時用
# --- 2. Webアプリ (Flask + Gunicorn) ---
web:
build:
context: . # プロジェクトのルートを基準にする
dockerfile: docker/python/Dockerfile
restart: always
# コマンド: Gunicornでアプリを起動 (バインド先はコンテナ内の全IP)
command: gunicorn --bind 0.0.0.0:8000 "app:create_app()"
volumes:
- .:/app # ホストのコードをコンテナに同期 (ホットリロード用)
depends_on:
- db
environment:
# SQLAlchemy用の接続URL
DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
FLASK_APP: run.py
FLASK_DEBUG: ${FLASK_DEBUG} # デバッグモードの設定は.envの値を使用
# --- 3. Webサーバー (Nginx) ---
nginx:
image: docker.io/library/nginx:alpine
restart: always
ports:
- "8080:80" # ブラウザからは http://localhost:8080 でアクセス
volumes:
- ./docker/nginx/conf.d:/etc/nginx/conf.d
- ./app/static:/app/static
volumes:
postgres_data: # DBデータを永続化するためのボリューム
Dockerfileを作成
「time_report/docker/python」ディレクトリに「Dockerfile」を作成します。
「Dockerfile」は、Webアプリ (Flask + Gunicorn)用コンテナの構成内容が記載されている設計図になります。
# 1. ベースイメージ FROM docker.io/library/python:3.12-slim # 2. uv を公式イメージからコピー COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv # 3. 環境変数の設定 ENV PYTHONUNBUFFERED=1 ENV PYTHONDONTWRITEBYTECODE=1 # 4. 作業ディレクトリ # 重要: .envを/opt/venvにインストールすることで、マウント時の消失を防ぐ WORKDIR /app ENV UV_PROJECT_ENVIRONMENT="/opt/venv" ENV PATH="/opt/venv/bin:$PATH" # 5. 依存ファイルのコピー # pyproject.toml と uv.lock をコピー COPY pyproject.toml uv.lock README.md ./ # 6. ライブラリのインストール # --frozen は「lockファイル通りに正確にインストールしろ」という命令 RUN uv sync --frozen --no-dev # 7. ソースコードのコピー COPY . . # 8. ポート公開 EXPOSE 8000 # 9. 起動コマンド # uv run ではなく、システムに入れた gunicorn を直接実行する CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:create_app()"]
Nginx設定ファイルの作成
「time_report/docker/nginx/conf.d」ディレクトリに、Nginxの設定ファイル(default.conf)を作成します。
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://web:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
.envの作成
データベースの設定や、Flaskの設定を記述した「.env」ファイルをルートディレクトリに作成します。
デバッグモードの設定は「docker-compose.yml 内で「FLASK_DEBUG: ${FLASK_DEBUG}」とすることで、「.env」ファイルの設定値を読み込ませています。
これにより、設定を一元管理でき、本番環境への移行もスムーズになります。
注意:このファイルは絶対にGitHub等に公開しないでください!
「.gitignore」に「.env」が設定されていることを必ず確認してください。
# PostgreSQL設定 DB_USER=timereport_user DB_PASSWORD=secret_password DB_NAME=timereport_db # Flask設定 SECRET_KEY=dev_secret FLASK_APP=run.py ## デバッグモード (1=ON, 0=OFF) FLASK_DEBUG=1
「.env」ファイルの内容を変更した場合は、コンテナを再起動することで設定を再読込させることができます。
テスト用のPythonコード作成
コンテナの動作確認用に、下記のPythonファイルを作成します。
- アプリ本体: time_report/app/__init__.py
- 起動ファイル: time_report/run.py
time_report/app/__init__.py
起動確認を行うために、Hello Worldを表示させる、テスト用のPythonプログラム「time_report/app/__init__.py」を作成します。
from flask import Flask
def create_app():
app = Flask(__name__)
@app.route('/')
def index():
return "<h1>Hello World !! Podman & Flask is running! </h1>"
return app
起動ファイル(time_report/run.py)の作成
デバッグ時に使用したり、データベースの変更を行う場合に使用するために必要なrun.pyを作成します。
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True)
なぜ run.py が必要なの?
現状コンテナを起動した際に、「docker-compose.yml」で設定した「CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:create_app()"]」が実行されるため、Gunicrornは「app」ディレクトリ内にある「/__init__.py」から「create_app」関数を直接実行し起動されるので、「run.py」は使用されません。
なので、「run.py」は不要に見えますが、以下の用途で必要になります。
- DB操作: flask db init などのコマンドが、アプリの場所を知るために参照します。
- デバッグ: python run.py と実行することで、Gunicornを使わずに簡易サーバでデバッグできます。
コンテナの起動
以下のファイルが作成できたら、プロジェクトのルートディレクトリ(docker-compose.ymlがある場所)で、「podman-compose -d up --build」コマンドを実行して、コンテナを起動します。
- docker-compose.yml
- .env
- Dockerfile
$ podman-compose -d up --build
--buildオプションについて
「--build」オプションは、コンテナの環境(「OSの設定」や「インストールするライブラリ」)を変更した場合に必要になります。
具体的には以下のファイルを変更した場合は、「--build」オプションが必要になります。
- pyproject.toml
- uv.lock
- Dockerfile
「docker-compose.yml」の「volumes:」として設定されていて、ボリュームとして同期されているpythonのファイルやhtmlファイル等の作成や変更を行った場合は、「--build」オプションは必要ありません。
コンテナの動作確認
「podman-compose ps」コマンドで、コンテナの起動状態を確認できます。
STATUSの欄が「Up」と表示されていれば、無事設定したコンテナが起動しています。
$ podman-compose ps podman-compose version: 1.0.6 ['podman', '--version', ''] using podman version: 4.9.3 podman ps -a --filter label=io.podman.compose.project=time_report CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e461619dafcb docker.io/library/postgres:15-alpine postgres 2 minutes ago Up 2 minutes 0.0.0.0:5432->5432/tcp time_report_db_1 512182f4df55 docker.io/library/nginx:alpine nginx -g daemon o... 2 minutes ago Up 2 minutes 0.0.0.0:8080->80/tcp time_report_nginx_1 f58d29ddb654 localhost/time_report_web:latest gunicorn --bind 0... 2 minutes ago Up 2 minutes time_report_web_1 exit code: 0
動作確認
設定に問題がなければ、Webブラウザで「http://localhost:8080」に接続すると、「time_report/app/__init__.py」で設定した「Hello World !! Podman & Flask is running!」と表示されれば構築完了です!
GitHubへ最新の状態をプッシュ
とりあえず今回の作業はここまでとするので、作成・編集したデータをコミットしてGitHubへpush(アップロード)しておきます。
まとめ:Podmanを使用した開発環境を作成
今回は、以下の作業を行いました。
- GitHubリポジトリの作成
- Podmanとpodman-composeを使用した開発環境の構築
- uvを使ったパッケージ管理の設定
- 作業内容をコミットしてGitHubへpush
新しくWebサービスを開発していくための土台ができたので、次回はいよいよ「ログイン・ログアウト機能」の実装に進みます。


コメント