
目標
- Flaskアプリケーションをデプロイする
デプロイ
前提条件
- Ubuntuサーバー が用意されていること(ローカルまたはクラウド環境)。
- サーバー上で Python 3 と pip がインストールされていること。
- サーバーに PostgreSQL がインストールされていること。
ローカル環境(アプリケーション)での準備
開発を終えて本番環境でアプリケーションを利用するには、いくつかのプログラムで本番環境用に書き換える必要があります。(分岐処理などを利用して構成している場合は設定値のみを書き換えるなどで済みますが、この記事では、少し多めの編集がかかります。)
この記事では、apps/main.py やいくつかのアプリケーションのapp.pyファイルに変更点があります。
「app.py」「.env」での編集
DATABASE_URI を環境変数で管理(ハードコーディングを避ける)
現在:
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///main.db"
変更後(PostgreSQLを使用、.envから取得):
from dotenv import load_dotenv
load_dotenv()
app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("DATABASE_URL")
load_dotenv()で環境変数を呼び出します。
.env
の例:
DATABASE_URL=postgresql://ユーザー名:パスワード@localhost/flaskr
※実際のデプロイではこのファイルは送られないので備忘録として入力しています。
DATABASE_URL=sqlite:///main.db
# DATABASE_URL=postgresql://flaskuser:flaskr123@localhost/flaskr
マイグレーション機能の追加(ローカル側)
本番環境で初回起動時にDBの作成をモデルから行います。これまでは開発環境だったため、db.create_all()を利用していましたが、本番環境ではデータが消失する恐れがあるため、これを利用しません。そのためマイグレーションツールをインストールして、マイグレーションコマンドで作成を行います。
$ pip install Flask-Migrate
実行結果

apps/main.pyファイルに次の行を追加します。
from dotenv import load_dotenv # load_dotenvをインポート <---------(追加した行)
from flask_migrate import Migrate # Migrateをインポート <---------(追加した行)
# Flask-Migrateの初期化
migrate = Migrate(app, db) <--------- # Migrateの初期化(追加した行)
SECRET_KEY も環境変数に移動
現在:
app.secret_key = "your_secret_key"
app.config["SECRET_KEY"] = os.urandom(24) # ←これで上書きされているので注意
変更後:
app.config["SECRET_KEY"] = os.getenv("SECRET_KEY", os.urandom(24))
.env に追記:
SECRET_KEY=s0m3$upeR$ecretKey123!
restore_database()
の実行タイミングの見直し(本番では初回のみ)
現在、毎回アプリ起動時に復元が走るようになっています。
restore_database()
→ 本番環境では restore_database()
を条件付きで動作させるか、管理スクリプトに切り分けると安全です。
編集後:
# restore_database を開発環境のときだけ実行
if os.getenv("FLASK_DEBUG") == "1":
restore_database()
.env に追記:本番では .env に FLASK_DEBUG=0 としておけば復元は行われません。
「FLASK_ENV」はFlask2.2以降では非推奨なのでコメントにします。
FLASK_DEBUG=0
# FLASK_ENV=development
socketio.run(debug=True)
→ 本番用では debug=False
or 削除
「discountapp」「productsapp」「tablesapp」「vatapp」
本番では、Gunicornなどで起動するため、開発モード限定の if __name__ == "__main__"
ブロックは使いません。
if __name__ == "__main__":
app = create_main_app()
socketio.run(app, host="0.0.0.0", port=5000, debug=False)
その他、次のアプリケーションにあるapp.py内の if __name__ == "__main__"
ブロックを削除します。「discountapp」「productsapp」「tablesapp」「vatapp」
# if __name__ == "__main__":
# from flask import Flask
# app = Flask(__name__)
# app.register_blueprint(vat_bp, url_prefix="/vat")
# app.run(debug=True)
この記事では、一旦すべてをコメントにしています。次はapps/main.pyファイルの全体です。
import os
from dotenv import load_dotenv # load_dotenvをインポート <---------(追加した行)
from flask import Flask
from flask_login import LoginManager
from flask_migrate import Migrate # Migrateをインポート <---------(追加した行)
from werkzeug.security import generate_password_hash
from apps.balanceapp.app import create_balance_app
from apps.common.app import create_common_app
from apps.common.db import db, restore_database
from apps.common.models import User
from apps.companyapp.app import create_company_app
from apps.discountapp.app import create_discount_app
from apps.expensesapp.app import create_expenses_app
from apps.ordersapp.app import create_orders_app, init_socketio, socketio
from apps.productsapp.app import create_products_app
from apps.qrcodeapp.app import create_qrcode_app
from apps.refundapp.app import create_refund_app
from apps.registerapp.app import create_register_app
from apps.tablesapp.app import create_tables_app
from apps.vatapp.app import create_vat_app
def create_main_app():
app = Flask(__name__) # 開発環境では"sqlite:///main.db"
load_dotenv() # .envの環境変数を読み込む
app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("DATABASE_URL")
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.secret_key = "your_secret_key" # <---------(この行を削除します)
# 画像アップロードの設定
UPLOAD_FOLDER = os.path.join(os.getcwd(), "apps", "productsapp", "static", "images")
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER
app.config["ALLOWED_EXTENSIONS"] = {"png", "jpg", "jpeg", "gif"}
app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024 # 最大16MB
# セッションの利用(24バイトのランダムなバイナリデータを使用)
app.config["SECRET_KEY"] = os.getenv("SECRET_KEY", os.urandom(24))
# DB初期化
db.init_app(app)
# Flask-Migrateの初期化
migrate = Migrate(app, db) <--------- # Migrateの初期化(追加した行)
# アプリケーションが最初のリクエストを受け取る前にデータベースをリストア(DBの復元処理を呼び出す)
# restore_database を開発環境のときだけ実行
if os.getenv("FLASK_DEBUG", "0") == "1":
restore_database()
# アプリコンテキスト内でテーブル作成
with app.app_context():
db.create_all()
if not User.query.filter_by(username="admin").first():
admin = User(
username="admin",
password=generate_password_hash("password123"),
is_admin=True,
)
db.session.add(admin)
db.session.commit()
# 各アプリをBlueprintとして登録
products_app = create_products_app(app)
app.register_blueprint(products_app, url_prefix="/products")
tables_app = create_tables_app(app)
app.register_blueprint(tables_app, url_prefix="/tables")
orders_app = create_orders_app(app)
app.register_blueprint(orders_app, url_prefix="/orders")
register_app = create_register_app(app)
app.register_blueprint(register_app, url_prefix="/register")
vat_app = create_vat_app(app)
app.register_blueprint(vat_app, url_prefix="/vat")
expenses_app = create_expenses_app(app)
app.register_blueprint(expenses_app, url_prefix="/expenses")
balance_app = create_balance_app(app)
app.register_blueprint(balance_app, url_prefix="/balance")
common_app = create_common_app(app)
app.register_blueprint(common_app, url_prefix="/common")
qrcode_app = create_qrcode_app(app)
app.register_blueprint(qrcode_app, url_prefix="/qrcode")
discount_app = create_discount_app(app)
app.register_blueprint(discount_app, url_prefix="/discount")
company_app = create_company_app(app)
app.register_blueprint(company_app, url_prefix="/company")
refund_app = create_refund_app(app)
app.register_blueprint(refund_app, url_prefix="/refund")
# ログインマネージャーの設定
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "common.login" # ログインページのルート名を指定
# Flask-Loginのユーザーローディング関数
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
# SocketIO初期化をここで呼び出す
init_socketio(app)
return app
if __name__ == "__main__":
app = create_main_app()
socketio.run(app, host="0.0.0.0", port=5000, debug=False)
次は.envファイルの全体です。
FLASK_APP=apps.main:create_main_app
FLASK_DEBUG=0
# FLASK_ENV=development
# FLASK_RUN_HOST=0.0.0.0
# FLASK_RUN_PORT=5000
DATABASE_URL=sqlite:///main.db
# DATABASE_URL=postgresql://flaskuser:flaskr123@localhost/flaskr
SECRET_KEY=s0m3$upeR$ecretKey123!
setup.py の作成と設定
まず、Flaskアプリケーションのパッケージを wheel形式 でビルドする準備をします。これには、setup.py ファイルが必要です。
setup.py ファイルは、Flaskアプリケーションの プロジェクトのルートディレクトリ に配置します。(アプリ全体をパッケージとしてビルド・配布する際の「基準ディレクトリ」です。)
FlaskProjフォルダの直下に「setup.py ファイル」を作成します。

次に、setup.pyファイル内に記述する必要のあるライブラリを取得します。venvの中で次のコマンドを入力します。
$ pip freeze > requirements.txt
実行をするとsetup.pyファイルの上あたりにrequirements.txtファイルが作成されます。

中身を確認すると次のように開発したflaskアプリケーションで利用しているライブラリが入力されています。この中で、setup.pyには、特に必要なものと、追加で「gunicorn」、「psycopg2-binary」を記述します。
また、開発でのみ必要だった「pre_commit, ruff, style, update」は本番環境に含めません。

次のように setup.pyファイルを入力します。
from setuptools import setup, find_packages
setup(
name="apps",
version="1.0.0",
packages=find_packages(),
include_package_data=True,
install_requires=[
"Flask==3.1.0",
"SQLAlchemy==2.0.38",
"python-dotenv==1.0.1",
"gunicorn>=20.0.0",
"psycopg2-binary>=2.9.0",
],
)
apps/qrcodeapp/app.py の修正
QRコード生成で利用するURLを修正します。
修正前
def generate_qr_url(table_id, token):
url = f"http://192.168.2.151:5000/orders/order/table/{table_id}?token={token}"
img = qrcode.make(url)
修正後
def generate_qr_url(table_id, token):
url = f"http://usvb.local/orders/order/table/{table_id}?token={token}"
img = qrcode.make(url)
全てのアプリケーションに「__init__.py」ファイルを作成します。appsディレクトリにも作成します。
ここまでの内容をコミットしておきます。
git status
git add .
git status
git commit –no-verify
Chore: 不要な if __name__ == "__main__"
ブロックの削除(20250425)
Chore: その他、ビルドの為の準備をおこなっています。(20250425)
開発環境から本番環境用にプログラムを変更しています。
COMMIT_EDITMSGを上書き保存して閉じます。
ここまでの編集をmainブランチへマージします。
git checkout main
git merge develop(本来はmainブランチもreleaseブランチからマージされます。)

developブランチは削除しません。
ビルド用パッケージのインストール
wheel を使ってビルドするためには、wheel をインストールしておく必要があります。
$ pip install wheel
次は実行結果です。

さらに build をインストールします。
$ pip install build
次は実行結果です。

pyproject.toml にビルド設定を追加
追加内容
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "flaskapp"
version = "1.0.0"
description = "My Flask System"
authors = [{name = "howahowa", email = "howahowa@email.com"}]
dependencies = [
"Flask",
"Flask-Login",
"Flask-Migrate",
"Flask-SQLAlchemy",
"python-dotenv",
]
[tool.setuptools]
packages = ["apps", "apps.balanceapp", "apps.common", "apps.companyapp", "apps.discountapp", "apps.expensesapp", "apps.ordersapp", "apps.productsapp", "apps.qrcodeapp", "apps.refundapp", "apps.registerapp", "apps.tablesapp", "apps.vatapp"]
[tool.setuptools.package-data]
"apps" = ["templates/**/*.html", "static/**/*.*"]
pyproject.toml ファイルの全体
[tool.ruff]
# 最大行長の設定
line-length = 88 # Blackのデフォルト値に合わせる
[tool.ruff.lint]
# 使用するルール (Flake8 + isort)
select = ["E", "F", "I"]
# 除外するルール
ignore = ["E501"] # E501: 長すぎる行を許容 (Blackと競合しやすいため)
# 除外するファイル・ディレクトリ
exclude = ["venv", "migrations", "__pycache__"]
# isort の設定 (インポート順序)
[tool.ruff.lint.isort]
known-first-party = ["app"]
combine-as-imports = true
# フォーマット設定 (Blackと同じ動作)
[tool.ruff.format]
docstring-code-format = true # docstring 内のコードも整形
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "flaskapp"
version = "1.0.0"
description = "My Flask System"
authors = [{name = "howahowa", email = "howahowa@email.com"}]
dependencies = [
"Flask",
"Flask-Login",
"Flask-Migrate",
"Flask-SQLAlchemy",
"python-dotenv",
]
[tool.setuptools]
packages = ["apps", "apps.balanceapp", "apps.common", "apps.companyapp", "apps.discountapp", "apps.expensesapp", "apps.ordersapp", "apps.productsapp", "apps.qrcodeapp", "apps.refundapp", "apps.registerapp", "apps.tablesapp", "apps.vatapp"]
[tool.setuptools.package-data]
"apps" = ["templates/**/*.html", "static/**/*.*"]
MANIFEST.inの作成
プロジェクトフォルダ直下(今回はFlaskProj)にMANIFEST.inを作成して次の内容を追加します。
recursive-include apps *.py
recursive-include apps/templates *.html
recursive-include apps/static *.*
アプリケーションのビルド
次に、アプリケーションを wheelパッケージ にビルドします。プロジェクトのルートディレクトリで以下のコマンドを実行します。
$ python -m build
このコマンドで、dist/ フォルダ内に .whl ファイル(例えば apps-1.0.0-py3-none-any.whl)が作成されます。

作成されたファイル

この .whl ファイル と requirements.txt を Ubuntuサーバー に転送します。
ここまでの内容をコミットしておきます。
git status
git add .
git status
git commit –no-verify
Chore: Flaskアプリケーションのビルド。(20250425)
Flaskアプリケーションのビルドを完了しています。
COMMIT_EDITMSGを上書き保存して閉じます。
ここまでの編集をmainブランチへマージします。
git checkout main
git merge develop(本来はmainブランチもreleaseブランチからマージされます。)
Ubuntuサーバーの準備
Ubuntuのインストールについてはこちらの記事を参考にしてください。

固定IPアドレス設定(VirtualBoxのUbuntu側)
仮想マシン(Ubuntu)に固定IPアドレスを割り当てます。VirtualBoxのネットワーク設定はブリッジアダプターを利用します。
Ubuntuにログイン後、固定IPアドレスを設定します。
「端末」アプリはUbuntu左下の「Show apps」アイコンをクリックして表示します。

以下、手順。
# 次のコマンドでnetplanディレクトリ内にあるyamlファイルを確認。
$ ls /etc/netplan/
# 存在するファイルの中身を sudo cat コマンドで確認し、dhcp4: true となっている設定ファイルを編集。
# 例えは、「01-network-manager-all.yaml 50-cloud-init.yaml」のように複数ある場合はそれぞれ中身を確認。
$ sudo cat /etc/netplan/01-network-manager-all.yaml
$ sudo cat /etc/netplan/50-cloud-init.yaml
(本記事での場合)01-network-manager-all.yamlの中身の確認。
$ sudo nano /etc/netplan/01-netcfg.yaml
(本記事での場合)50-cloud-init.yamlの中身の確認。今回はこちらを編集
$ sudo cat /etc/netplan/50-cloud-init.yaml
viエディタ、もしくは、nanoエディタでファイルを開きます。
$ sudo cat /etc/netplan/50-cloud-init.yaml
ファイルの中身を次のように編集します。
network:
version: 2
renderer: networkd
ethernets:
enp0s3:
dhcp4: no
addresses:
- 192.168.2.152/24
routes:
- to: default
via: 192.168.2.1 # default gatewayとして設定
nameservers:
addresses:
- 8.8.8.8
- 8.8.4.4
「/etc/netplan/01-network-manager-all.yamlファイル」と「/etc/netplan/50-cloud-init.yamlファイル」で、その他のユーザーが読み書き不能にする。
$ sudo chmod 600 /etc/netplan/01-network-manager-all.yaml
$ sudo chmod 600 /etc/netplan/50-cloud-init.yaml
設定後、ネットワークを適用します。
$ sudo netplan apply
次のエラーが表示された場合は下のコマンドを利用します。

$ sudo systemctl enable systemd-networkd
$ sudo systemctl start systemd-networkd
更に次のエラーが表示された場合はsystemd-networkdをインストールします。

$ sudo apt update
$ sudo apt install systemd-networkd
#もし NetworkManager が動作している場合は、それを無効化して再試行します。
$ sudo systemctl status NetworkManager
$ sudo systemctl stop NetworkManager
$ sudo systemctl disable NetworkManager
$ sudo systemctl enable systemd-networkd
$ sudo systemctl start systemd-networkd
$ sudo netplan apply
次のように表示されれば成功です。

Avahi の設定手順(Ubuntuサーバー)
次のコマンドでUbuntuにAvahi をインストールします。
$ sudo apt install avahi-daemon -y
次はインストール済みの場合の画面です。

次のコマンドでAvahi デーモンの有効化・起動を行います。
$ sudo systemctl enable avahi-daemon
$ sudo systemctl start avahi-daemon
現在のホスト名を確認します。(この記事の場合:ubsv)
$ hostname

usvb.local
で名前解決されることを確認します。(別のPCから)
Windowsの場合はコマンドプロンプトを立ち上げて次のように入力します。
$ ping ubsv.local
http://ubsvでアクセスできるように/etc/hostsを編集します。
$ sudo nano /etc/hosts
内容を次のように編集します。
127.0.0.1 localhost
192.168.2.152 ubsv.local
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
Windows端末での設定
C:\Windows\System32\drivers\etc\hostsに一番下の一行を追加
# localhost name resolution is handled within DNS itself.
# 127.0.0.1 localhost
# ::1 localhost
192.168.2.152 ubsv.local
コマンドプロンプトから設定する場合は管理者権限で開いて次のように入力します。
C:\Windows\System32>echo 192.168.2.152 ubsv.local >> C:\Windows\System32\drivers\etc\hosts
Python 3 と pipのインストール
Ubuntuサーバーに Python 3 と pip がインストールされていない場合、以下のコマンドでインストールします。
$ sudo apt install python3 python3-pip python3-venv
PostgreSQLのインストール
Flaskアプリケーションが PostgreSQL を利用する場合、サーバーに PostgreSQL をインストールする必要があります。
$ sudo apt install postgresql postgresql-contrib
インストール後、PostgreSQLのサービスが起動していることを確認します。
$ sudo systemctl start postgresql
$ sudo systemctl enable postgresql
PostgreSQLデータベースの設定
PostgreSQLのデータベースとユーザーを作成します。ここでは、flaskr というデータベースとユーザー flaskuser (パスワード:flaskr123)を作成します。
$ sudo -u postgres psql
postgres=# CREATE DATABASE flaskr;
postgres=# CREATE USER flaskuser WITH PASSWORD 'flaskr123';
postgres=# ALTER ROLE flaskuser SET client_encoding TO 'utf8';
postgres=# ALTER ROLE flaskuser SET default_transaction_isolation TO 'read committed';
postgres=# ALTER ROLE flaskuser SET timezone TO 'UTC';
postgres=# CREATE SCHEMA public;
postgres=# ALTER SCHEMA public OWNER TO flaskuser;
postgres=# GRANT ALL PRIVILEGES ON DATABASE flaskr TO flaskuser;
postgres=# GRANT CREATE ON SCHEMA public TO flaskuser;
postgres=# GRANT USAGE ON SCHEMA public TO flaskuser;
postgres=# GRANT ALL PRIVILEGES ON SCHEMA public TO flaskuser;
postgres=# GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO flaskuser;
postgres=# GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO flaskuser;
postgres=# \q
これで、flaskr データベースと flaskuser ユーザーが作成されました。
Flaskアプリケーションのデプロイ
Ubuntuサーバー上での仮想環境の作成
Ubuntuサーバー上でアプリケーションを動かすために、Pythonの仮想環境を作成します。
$ sudo mkdir /home/username/flaskapp
$ cd /home/username/flaskapp
$ sudo python3 -m venv venv
$ sudo source venv/bin/activate
※この記事の場合の入力
$ sudo mkdir /home/ubuntu-server/flaskapp
$ cd /home/ubuntu-server/flaskapp
$ sudo python3 -m venv venv
$ source venv/bin/activate

次のコマンドで仮想環境を抜けます。続けてcdコマンドでホームディレクトリに戻ります。
$ deactivate
$ cd
.whl ファイルのインストール
ローカルで作成した .whl ファイルを Ubuntuサーバーに転送します。この記事ではFileZilaを利用します。
別の転送方法として、scp や rsync を使うことができます。例えば、scpを使う場合は以下のようにします。
$ scp flaskapp-1.0.0-py3-none-any.whl username@your-ubuntu-server:/home/username/flaskapp
Windowsのコマンドプロンプトからssh接続を行う場合は次のコマンドを入力します。(この記事の設定の場合)
$ ssh ubuntu-server@192.168.2.152
UbuntuアプリケーションサーバーへSSHサーバのインストールをして起動を行います。
$ sudo apt install openssh-server
$ sudo systemctl enable ssh
$ sudo systemctl start ssh
SSHが正常に起動しているかの確認を行います。
$ sudo systemctl status ssh
次のように表示されれば成功です。

ファイアウォールの設定で22番ポートを開放します。
$ sudo ufw allow ssh
WindowsからFileZilaでubuntu-serverへ接続します。
FileZilaを起動します。

次のように設定し接続します。
「ファイル」タブから「サイトマネージャー」を選択します。

表示されるダイアログボックスで「新しいサイト」をクリックします。

「新しいサイト」の名前を「UbuntuServer」に変更します。
次のホスト情報を入力します。
プロトコルに「SFTP-SSH File Transfer Protocol」
ホストにubuntu-serverのIPアドレス(今回は192.168.2.152)
ログオンタイプに「普通」
次の設定はUbuntuをインストールしたときのユーザー名とパスワードです。いつもUbuntuを起動してサインインをするときのものです。
ユーザーにアクセスを行うユーザー名(今回はubuntu_server)
パスワードにユーザーのパスワード(今回はubuntu2401)
接続が完了すると次のように表示されます。

「.whl ファイル」「requirements.txtファイル」を ドラッグアンドドロップでUbuntuサーバーに転送します。
転送先は「home/username(今回はubuntus_server)/flaskapp」です。
転送後は次のように表示されます。

requirements.txtファイルにはまた、開発でのみ必要だった「pre_commit, ruff, style, update」は含めません。

※Ubuntu側でカレントディレクトリを移動します。この記事の場合の入力
$ cd /home/ubuntu-server/flaskapp
$ source venv/bin/activate
Ubuntuサーバーに転送後、仮想環境内で .whl ファイルをインストールします。
$ pip install flaskapp-1.0.0-py3-none-any.whl
インストールが完了すると次のような画面が表示されます。

次のコマンドでrequirements.txtに書かれたライブラリをインストールします。.whlファイルをインストールした時と、重複するものがあってもpipが調整してくれます。
pip install -r requirements.txt
インストールが完了すると次のように表示されます。

データベースの設定
pythonからPostgresqlに接続するために「psycopg2」をインストールします。
$ sudo apt install libpq-dev
$ pip install psycopg2


flask アプリケーションが使用するデータベースを設定します。以下のコマンドを実行して、マイグレーションを行えるようにします。
$ pip install flask-migrate

ここでは、flask アプリケーションエントリーポイントなどを念のために指定します。
$ export FLASK_APP=apps.main:create_main_app
$ export FLASK_DEBUG=0
$ export DATABASE_URL=postgresql://flaskuser:flaskr123@localhost/flaskr
$ export SECRET_KEY=s0m3$upeR$ecretKey123!
するデータベースを設定します。以下のコマンドを実行して、マイグレーションを行えるようにします。
$ flask db init
# flask db init は、マイグレーションのための設定ファイル
# (migrations ディレクトリ)を作成するコマンドです。
# このコマンド自体はまだデータベースを作成しません。
$ flask db migrate -m "Initial migration"
# このコマンドで、実際にデータベーススキーマ(テーブルの構造)を作成するための、
# マイグレーションファイルを生成します。このコマンドはFlaskのモデルに基づいて、
# テーブル作成のためのSQLを生成します。
$ flask db upgrade
# 実際にデータベースにその変更を適用します。
# migrate コマンドによって生成された変更内容が
# データベースに適用され、テーブルが作成されます。
これにより、flaskr のテーブルが PostgreSQL の flaskr データベースに作成されますが、次にあげる2種類のエラーが出る場合はそれぞれ対応が必要です。
❶もし次のようなエラーが出たら「userモデル」のStringの長さを変更する必要があります。

apps/common/models.pyファイル
from flask_login import UserMixin
from apps.common.db import db
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(200), nullable=False, unique=True)
password = db.Column(db.String(512), nullable=False)
is_admin = db.Column(db.Boolean, default=False)
def __repr__(self):
return f"<User {self.username}>"
再度、ビルドしてファイルをサーバー側に再送します。
❷次のエラーが出た場合はPostgerSQLのユーザーの権限を変更する必要があります。

その場合はデータベースとユーザーを次のように作り直して適切な権限を割り当てます。
$ sudo -u postgres psql
postgres=# DROP DATABASE IF EXISTS flaskr;
postgres=# DROP OWNED BY flaskuser;
postgres=# DROP ROLE IF EXISTS flaskuser;
postgres=# CREATE DATABASE flaskr;
postgres=# CREATE USER flaskuser WITH PASSWORD 'flaskr123';
postgres=# ALTER ROLE flaskuser SET client_encoding TO 'utf8';
postgres=# ALTER ROLE flaskuser SET default_transaction_isolation TO 'read committed';
postgres=# ALTER ROLE flaskuser SET timezone TO 'UTC';
postgres=# CREATE SCHEMA public;
postgres=# ALTER SCHEMA public OWNER TO flaskuser;
postgres=# \c flaskr
flaskr=# GRANT ALL PRIVILEGES ON DATABASE flaskr TO flaskuser;
flaskr=# GRANT CREATE ON SCHEMA public TO flaskuser;
flaskr=# GRANT USAGE ON SCHEMA public TO flaskuser;
flaskr=# GRANT ALL PRIVILEGES ON SCHEMA public TO flaskuser;
flaskr=# GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO flaskuser;
flaskr=# GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO flaskuser;
postgres=# \q
エラーについて、まとめると、再度、ローカル側でビルドしてサーバー側に送りインストールします。この時、以前のインストールした内容はアンインストールし、venvも削除します。また、事前にデータベースも次のように作り直します。
$ sudo -u postgres psql
postgres=# DROP DATABASE IF EXISTS flaskr;
postgres=# DROP OWNED BY flaskuser;
postgres=# DROP ROLE IF EXISTS flaskuser;
postgres=# CREATE DATABASE flaskr;
postgres=# CREATE USER flaskuser WITH PASSWORD 'flaskr123';
postgres=# ALTER ROLE flaskuser SET client_encoding TO 'utf8';
postgres=# ALTER ROLE flaskuser SET default_transaction_isolation TO 'read committed';
postgres=# ALTER ROLE flaskuser SET timezone TO 'UTC';
postgres=# CREATE SCHEMA public;
postgres=# ALTER SCHEMA public OWNER TO flaskuser;
postgres=# \c flaskr
flaskr=# GRANT ALL PRIVILEGES ON DATABASE flaskr TO flaskuser;
flaskr=# GRANT CREATE ON SCHEMA public TO flaskuser;
flaskr=# GRANT USAGE ON SCHEMA public TO flaskuser;
flaskr=# GRANT ALL PRIVILEGES ON SCHEMA public TO flaskuser;
flaskr=# GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO flaskuser;
flaskr=# GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO flaskuser;
postgres=# \q
$ pip uninstall apps
$ rm -rf venv
$ rm -rf apps
$ rm -rf flaskapp-1.0.0-py3-none-any.whl
$ sudo python3 -m venv venv
$ source venv/bin/activate
$ sudo chown -R ubuntu-server:ubuntu-server /home/ubuntu-server/flaskapp/venv
$ pip install flaskapp-1.0.0-py3-none-any.whl
$ pip install -r requirements.txt
$ sudo apt install libpq-dev
$ pip install psycopg2
$ pip install flask-migrate
$ export FLASK_APP=apps.main:create_main_app
$ export FLASK_DEBUG=0
$ export DATABASE_URL=postgresql://flaskuser:flaskr123@localhost/flaskr
$ export SECRET_KEY=s0m3$upeR$ecretKey123!
$ flask db init
$ flask db migrate -m "Initial migration"
$ flask db upgrade
$ flask db initコマンド

$ flask db migrate -m “Initial migration”コマンド(””の中はコメントです。)

$ flask db upgrade

次のように入力すると作成されたテーブルを確認できます。
$ sudo -u postgres psql
postgres=# \c flaskr
flaskr=# \dt
flaskr=# \q

データベースを完全初期化したい場合の手順:本番環境で慎重に扱ってください。
データベースの全テーブルを削除
$ flask db downgrade base # 可能なら初期状態に戻します。
もしくは手動でテーブルを全削除
$ psql -U postgres -d flaskr
flaskr=# DROP SCHEMA public CASCADE;
flaskr=# CREATE SCHEMA public;
再度マイグレーションを適用して初期化
$ flask db upgrade
GunicornとNginxのインストールと設定・起動
Gunicornのインストール
GunicornはFlaskアプリケーションを本番環境で動作させるために使うWSGIサーバです。仮想環境にインストールします。(.whlファイルをインストール時に追加されているはずですが、念のためインストールを実行します。)
$ pip install gunicorn
既にインストールされている場合は次のように表示されます。

Gunicornでアプリケーションを起動
以下のコマンドでGunicornを使ってFlaskアプリケーションを起動します。
$ gunicorn --workers 4 --threads 2 --bind 127.0.0.1:8000 apps.main:create_main_app
このコマンドは、gunicorn を使って、apps/main.py の create_main_app()にある Flask アプリケーションインスタンス app を起動するコマンドです。各部分の意味は次の通りです。
gunicornは WSGI HTTP サーバーで、FlaskやDjangoようなPython製Webアプリケーションを本番環境で動かすために使われます。Flaskの組み込みサーバー(flask run)は開発用です。本番環境では、一般的に gunicorn などのWSGIサーバーを使います。
–workers はワーカープロセスの数を指定します。このオプションによって、リクエストを並行して処理するために複数のワーカープロセスが起動します。(今回のプログラムでは利用しません。)
4 は4つのワーカープロセスを使用することを意味します。リクエスト数が多い場合や同時アクセスが高い場合には、ワーカー数を増やすと性能が向上することがあります。(今回のプログラムでは利用しません。)
–threads は各ワーカープロセス内で使用するスレッドの数を指定します。これにより、1つのワーカー内で複数のリクエストを並行して処理できます。(今回のプログラムでは利用しません。)
2 は各ワーカーが2つのスレッドを使用することを意味します。(今回のプログラムでは利用しません。)
–bind はアプリケーションをどのIPアドレスとポートで待機させるかを指定します。
127.0.0.1 はローカルホスト(自身のコンピュータ)を意味し、外部からのアクセスを拒否します。インターネット越しにアクセスする場合は、0.0.0.0 や外部IPアドレスを指定します。
8000 はポート番号です。指定されたポートでアプリケーションが待機します。この場合、http://127.0.0.1:8000 でアクセス可能です。
apps:app は、Flaskアプリケーションのインポートパスを指定します。
apps は、Flaskアプリケーションが保存されているPythonモジュール(通常はディレクトリ名)です。
app は、そのモジュール内で定義されたFlaskアプリケーションオブジェクトの名前です。app オブジェクトは通常、Flaskのインスタンスです。
実行すると次のように表示されます。

gunicorn を停止するには、ターミナル上で、Ctrl + C を押します。

Nginxの設定
Nginxをリバースプロキシとして設定して、Flaskアプリケーションへトラフィックを送るようにします。
Nginxのインストール
$ sudo apt install nginx
Nginxの設定ファイルを作成/変更します。
$ sudo nano /etc/nginx/sites-available/flaskapp
以下の内容を追加します。
server {
listen 80;
server_name ubsv.local 192.168.2.152;
location / {
proxy_pass http://127.0.0.1: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;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Session-Id $http_x_session_id;
proxy_set_header X-CSRF-Token $http_x_csrf_token;
}
location /socket.io/ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Session-Id $http_x_session_id;
proxy_set_header X-CSRF-Token $http_x_csrf_token;
proxy_buffering off;
gzip off;
}
}
設定を有効にします。
$ sudo ln -s /etc/nginx/sites-available/flaskapp /etc/nginx/sites-enabled
# 上のコマンドは上書き保存ができません。ファイルを修正して保存する場合は次のコマンドを利用します。
# sudo ln -sf /etc/nginx/sites-available/flaskapp /etc/nginx/sites-enabled/
$ sudo systemctl reload nginx
NginxのHTTPS化(Let’s Encrypt)
本番運用ならHTTPS(SSL証明書)は必須です。Let’s Encrypt + certbot を使うのが無料で簡単です。(この記事では行っていません。)
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.example.com
アプリケーションの永続化
最後に、Flaskアプリケーションを 永続的に実行 するために systemd を使ってサービスとして登録します。
Flaskアプリケーション用のsystemdサービスファイルの作成
$ sudo nano /etc/systemd/system/flaskapp.service
以下の内容を入力します。
[Unit]
Description=Flask Application
After=network.target
[Service]
User=ubuntu-server
Group=ubuntu-server
WorkingDirectory=/home/ubuntu-server/flaskapp
Environment="FLASK_DEBUG=0"
Environment="DATABASE_URL=postgresql://flaskuser:flaskr123@localhost/flaskr"
Environment="SECRET_KEY=s0m3$upeR$ecretKey123!"
Environment="PATH=/home/ubuntu-server/flaskapp/venv/bin"
ExecStart=/home/ubuntu-server/flaskapp/venv/bin/gunicorn -k gevent -w 1 -b 127.0.0.1:8000 apps.main:create_main_app
Restart=always
[Install]
WantedBy=multi-user.target
SocketIO では WebSocket を使用します。ですが、Gunicorn のデフォルト(sync や gthread)では WebSocket に完全対応していません。上の「ExecStart」で、次のように設定しました。「/home/ubuntu-server/flaskapp/venv/bin/gunicorn -k gevent -w 1 -b 127.0.0.1:8000 apps.main:create_main_app」
geventをインストールします。
$ pip install gevent
これで、gunicornコマンドと同じ内容のSystemdサービスファイルが作成されます。保存後、サービスの設定を反映させるために以下のコマンドを実行します。
$ sudo systemctl daemon-reload
$ sudo systemctl start flaskapp
$ sudo systemctl restart flaskapp #再起動の場合
$ sudo systemctl enable flaskapp
次のエラーが出た場合は create_main_app(): 部分を次のように修正して再ビルドし、再インストールする必要があります。
def create_main_app(app=None, config=None):
app = app or Flask(__name__)
$ sudo systemctl status flaskapp

これでつながらない場合は、各ファイルを転送してappsディレクトリに格納します。(このとき、各ディレクトリにある__pycache__は転送から除外します。)

その他の修正
開発環境から本番環境へ移行すると、開発環境では思うように動いてくれたプログラムが意図しない動きになることがあります。このデプロイでも、設定ファイルを、今回の動きに合うように設定し、本番環境でのみ追加で必要なパッケージをインストールしたりと修正を加えています。
さらに、プログラム自体にも変更を加えています。追加で行ったプログラムの修正は次の通りです。
apps/main.py
import os
from datetime import timedelta
from dotenv import load_dotenv
from flask import Flask
from flask_login import LoginManager
from flask_migrate import Migrate # Migrateをインポート
from werkzeug.security import generate_password_hash
from apps.balanceapp.app import create_balance_app
from apps.common.app import create_common_app
from apps.common.db import db, restore_database
from apps.common.models import User
from apps.companyapp.app import create_company_app
from apps.discountapp.app import create_discount_app
from apps.expensesapp.app import create_expenses_app
from apps.ordersapp.app import create_orders_app, init_socketio, socketio
from apps.productsapp.app import create_products_app
from apps.qrcodeapp.app import create_qrcode_app
from apps.refundapp.app import create_refund_app
from apps.registerapp.app import create_register_app
from apps.tablesapp.app import create_tables_app
from apps.vatapp.app import create_vat_app
def create_main_app():
app = Flask(__name__) # 開発環境では"sqlite:///main.db"
if os.getenv("FLASK_DEBUG", "0") == "1":
load_dotenv(override=True) # .envの環境変数を読み込む
# print(f"DATABASE_URL = {os.getenv('DATABASE_URL')}")
app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("DATABASE_URL")
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
# 画像アップロードの設定
UPLOAD_FOLDER = os.path.join(os.getcwd(), "apps", "productsapp", "static", "images")
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER
app.config["ALLOWED_EXTENSIONS"] = {"png", "jpg", "jpeg", "gif"}
app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024 # 最大16MB
# セッションの利用(24バイトのランダムなバイナリデータを使用)
app.config["SECRET_KEY"] = os.getenv("SECRET_KEY", os.urandom(24))
# DB初期化
db.init_app(app)
# Flask-Migrateの初期化
migrate = Migrate(app, db) # Migrateの初期化
# アプリケーションが最初のリクエストを受け取る前にデータベースをリストア(DBの復元処理を呼び出す)
# restore_database を開発環境のときだけ実行
if os.getenv("FLASK_DEBUG", "0") == "1":
restore_database()
# アプリコンテキスト内でテーブル作成
with app.app_context():
db.create_all()
if not User.query.filter_by(username="admin").first():
admin = User(
username="admin",
password=generate_password_hash("password123"),
is_admin=True,
)
db.session.add(admin)
db.session.commit()
# 各アプリをBlueprintとして登録
products_app = create_products_app(app)
app.register_blueprint(products_app, url_prefix="/products")
tables_app = create_tables_app(app)
app.register_blueprint(tables_app, url_prefix="/tables")
orders_app = create_orders_app(app)
app.register_blueprint(orders_app, url_prefix="/orders")
register_app = create_register_app(app)
app.register_blueprint(register_app, url_prefix="/register")
vat_app = create_vat_app(app)
app.register_blueprint(vat_app, url_prefix="/vat")
expenses_app = create_expenses_app(app)
app.register_blueprint(expenses_app, url_prefix="/expenses")
balance_app = create_balance_app(app)
app.register_blueprint(balance_app, url_prefix="/balance")
common_app = create_common_app(app)
app.register_blueprint(common_app, url_prefix="/common")
qrcode_app = create_qrcode_app(app)
app.register_blueprint(qrcode_app, url_prefix="/qrcode")
discount_app = create_discount_app(app)
app.register_blueprint(discount_app, url_prefix="/discount")
company_app = create_company_app(app)
app.register_blueprint(company_app, url_prefix="/company")
refund_app = create_refund_app(app)
app.register_blueprint(refund_app, url_prefix="/refund")
# ログインマネージャーの設定
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "common.login" # ログインページのルート名を指定
app.config["SESSION_PROTECTION"] = "strong"
app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(minutes=30)
# Flask-Loginのユーザーローディング関数
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
# SocketIO初期化をここで呼び出す
init_socketio(app)
return app
# if __name__ == "__main__":
# app = create_main_app()
# socketio.run(app, host="0.0.0.0", port=5000, debug=False)
apps/registerapp/app.py:ルート:view_bill(order_byを追加)
# 指定された bill_id に関連する注文を取得
orders = Order.query.filter_by(bill_id=bill.id).order_by(Order.id.asc()).all()
# 返金用のオブジェクトの取得
refunded_orders = (
RefundBill.query.filter_by(original_bill_id=bill.id)
.order_by(RefundBill.id.asc())
.all()
)
apps/registerapp/app.py:ルート:pay_bill(intを追加)
int(order.total * (100 - (order.discount_number or 0)) / 100)
+ int(
int(order.total * (100 - (order.discount_number or 0)) / 100)
apps/refundapp/app.py:ルート:refund_bill(order_byを追加)
# 指定された bill_id に関連する注文を取得
orders = Order.query.filter_by(bill_id=bill.id).order_by(Order.id.asc()).all()
# 返金用のオブジェクトの取得
refunded_orders = (
RefundBill.query.filter_by(original_bill_id=bill.id)
.order_by(RefundBill.id.asc())
.all()
)
apps/qrcodeapp/app.py:ルート:qrcode_generate(bill_idに関する内容をコメント化)
if request.method == "POST":
table_id = int(request.form.get("table_id"))
# すでに有効なトークン(未会計のもの)が存在するか確認
existing_token = (
db.session.query(TableAccessToken)
.filter(
TableAccessToken.table_id == table_id,
# TableAccessToken.bill_id.is_(None), # 未会計
TableAccessToken.expires_at > datetime.now(timezone.utc), # 有効期限内
apps/qrcodeapp/app.py:ルート:generate_qr_url(URLに次のアドレスを指定)
url = f"http://192.168.2.152/orders/order/table/{table_id}?token={token}"
apps/ordersapp/app.py:ルート:order_menu(紐づけのプログラム追加)
# トークンにbill_idを紐づける
token_obj.bill_id = bill.id
db.session.commit()
apps/common/models.py:usernameとpasswordの指定文字数を変更
username = db.Column(db.String(200), nullable=False, unique=True)
password = db.Column(db.String(512), nullable=False)
apps/balanceapp/app.py:ルート:monthly_balance(balanceの算出方法修正)
# データ保存
date_dt = current_day
all_days[date_dt] = {
"orders": daily_orders,
"cash": daily_cash,
"cashless": daily_cashless,
"refunds": daily_refunds,
"exincome": daily_ex_in,
"exexpense": daily_ex_out,
"pbincome": daily_pb_in,
"pbexpense": daily_pb_out,
"balance": daily_cash
+ daily_cashless
- daily_refunds
+ daily_ex_in
- daily_ex_out
+ daily_pb_in
- daily_pb_out,
}
apps/balanceapp/templates/balance_daily.html(次のように表示を変更)
<table>
<tr>
<th>当日売上</th>
</tr>
<tr>
<td>¥{{
(total_cash +
total_cashless +
total_refund) | int
}}</td>
</tr>
<tr>
<th>当日現金収支</th>
</tr>
<tr>
<td>¥{{
(total_cash +
total_refund +
total_ex_in -
total_ex_ex) | int
}}</td>
</tr>
<tr>
<th>当日最終収支(普通預金込み)</th>
</tr>
<tr>
<td>¥{{
(total_cash +
total_cashless +
total_refund +
total_ex_in -
total_ex_ex +
total_pb_in -
total_pb_ex) | int
}}</td>
</tr>
</table>
apps/balanceapp/templates/balance_monthly.html(次のように表示を変更)
<h3>収支概要</h3>
<table>
<tr>
<th>月間売上(現金 + キャッシュレス - 返金)</th>
</tr>
<tr>
<td>¥{{ monthly_sales - monthly_refund | int }}</td>
</tr>
<tr>
<th>月間最終収支(現金収支 + 普通預金 収支)</th>
</tr>
<tr>
<td>¥{{ total_balance | int }}</td>
</tr>
<tr>
<th>月間売上から引かれた返金の総額</th>
</tr>
<tr>
<td>¥{{ monthly_refund | int }}</td>
</tr>
</table>
今回は以上になります。

ブックマークのすすめ
「ほわほわぶろぐ」を常に検索するのが面倒だという方はブックマークをお勧めします。ブックマークの設定は別記事にて掲載しています。

