はじめに
現役のトラックドライバーとして物流の現場を走りながら、Pythonを独学しているtosane932です。(学習127時間)
前回の記事(第2便)では、使い慣れていた db.create_all() を断捨離し、Flask-Migrate によるDBの変更履歴管理(マイグレーション)の基盤を整えるところまでを書きました。
今回は、一連の環境整備のなかで同時に実施した、ローカル環境の SQLite を卒業して本番環境(Render)の PostgreSQL へ完全移行を達成するまでの記録です。インフラを切り替えるなかで直面した、Render特有の環境変数の仕様による接続エラー(プロトコル非サポート)の壁、そして「コードは直したはずなのに、なぜか本番ログが SQLite のまま」という想定外の珍事に振り回されたプロセスまで、事実経過をそのまま共有します。
1. 移行の背景と目的
これまでの本番環境(Render)では、永続化ディスクを持たない簡易的な SQLite の運用を行っていました。しかし、Renderの無料プラン仕様などにより、デプロイや再起動のタイミングでDBデータがリセットされてしまう課題がありました。
実運用に耐えうるデータ駆動の管理能力を担保するため、データが永続化される本番用データベース(Render PostgreSQL Freeプラン)への完全移行、およびデプロイ時にマイグレーションが自動実行されるパイプラインの構築を目指しました。
2. 対応手順と事実経過
① Render環境への対応(config.py)における接続URL置換ロジックの実装
Renderが自動生成する DATABASE_URL の先頭文字列は postgres:// となっていますが、SQLAlchemyの最新仕様ではこのプロトコル名がサポートされておらず、そのままでは接続エラーが発生します。
この仕様上のズレを安全に吸収するため、config.py にて動的に文字列を置換して読み込む安全パッチを以下の通り実装しました。
import os
raw_db_url = os.environ.get("DATABASE_URL")
if raw_db_url:
# 先頭が「postgres://」の場合、SQLAlchemyが解釈できる「postgresql://」に置換
if raw_db_url.startswith("postgres://"):
SQLALCHEMY_DATABASE_URI = raw_db_url.replace("postgres://", "postgresql://", 1)
else:
SQLALCHEMY_DATABASE_URI = raw_db_url
else:
# 環境変数がない場合はローカルのSQLiteを使用(フォールバック)
SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join(os.path.dirname(__file__), 'local.db')}"
コードを修正し、コミット・プッシュも完了させました。
しかし、本番環境では期待した結果になりませんでした。
② Dockerfileへの自動マイグレーション組み込み
デプロイと同時にDBのスキーマが常に最新状態(upgrade)になるよう、本番運用の Dockerfile の起動コマンドに flask db upgrade を統合しました。
CMD flask db upgrade && python app.py
これにより、コンテナが起動する瞬間に自動的にマイグレーション処理が実行される仕組みが完成しました。
③ 見えない壁:直したはずなのに、本番ログがSQLiteのままだった
①②の修正を終え、意気揚々とデプロイしたところ、ログにはこう表示されました。
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
postgresql:// への置換ロジックは実装したはずなのに、接続先は相変わらず SQLiteImpl (SQLite) のまま。まずはコードそのものを疑い、ローカルで実際のファイルを確認しました。
cat config.py
出力を確認したところ、置換ロジックはきちんとファイルに反映されています。念のためコミット漏れも疑い、以下を実行しました。
git commit -m "Fix: Replace postgres:// with postgresql:// for Render PostgreSQL connection"
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
nothing to commit という結果は、「すでに全く同じ内容がコミット・プッシュ済みである」ことを意味します。つまり、コードもコミットも間違っていませんでした。
ここで発想を変え、そもそも本番側にPostgreSQLのデータベース自体が存在しているかを確認したところ、原因が判明しました。Renderの管理画面を確認すると、本番用のPostgreSQL データベースがまだ一度も作成されていなかったのです。接続コードだけを先に用意していて、肝心の「繋ぎ先」がまだ存在していなかった、という状態でした。
④ Render上でのPostgreSQLデータベース新規作成
Renderのダッシュボードより、新規にPostgreSQL(Freeプラン)を立ち上げました。ステータスが Available(利用可能)になるのを待ち、発行された Internal Database URL を確認。Web Service側の環境変数 DATABASE_URL にこの値を設定しました。
環境変数の保存をトリガーに、Render側で自動的に再デプロイが実行されました。
デプロイ成功時のログ:
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
==> Your service is live 🎉
ログより、接続先が PostgresqlImpl(PostgreSQL)に正常に切り替わり、テーブルスキーマの適用を経てWebサービスが正常に開通したことを確認しました。
3. まとめ
今回の環境整備およびインフラ移行を通じて、以下の本質的な開発基盤を確立することができました。
- 1.環境の同期:
requirements.txtの厳密化とpytestによるバージョン不一致リスクの排除 - 2.運用の近代化:
db.create_all()の断捨離とFlask-Migrateによる履歴管理化 - 3.堅牢なインフラ: 本番環境(
PostgreSQL)への完全移行と、環境変数置換によるエラーの動的解決
今回、一番の学びだったのは
「コードが正しいこと」 と
「インフラが実際に用意されていること」
は別問題だということです。接続コードだけを先に完成させても、繋ぎ先のデータベースが存在しなければ意味がありません。
ローカルコードと本番インフラが噛み合って、初めてデータが消えない堅牢なアプリケーション基盤が完成しました。
今回の集中した取り組みで、実務標準の安全な開発・運用サイクルへと大きくステップアップすることができました。ここからさらに学びを深め、アプリケーションを進化させていきます。
最後までお読みいただき、ありがとうございました!
sales_data_app 整備記録シリーズ
「本番出走前・車検三部作」
🚚「出走後メンテナンスシリーズ」
🍞
