Flaskサービスを1コマンドでデプロイできる環境を作った話(VPS / Gunicorn / systemd / Nginx / SSL 自動化)

Infra / DevOps

はじめに

個人開発で複数の Web サービスを並行して作っていると、
インフラ構築の作業が毎回ほぼ同じで、正直かなり面倒だと感じてきた。

  • ディレクトリ作成Python venv
  • Flask / Gunicorn インストール
  • systemd サービス作成
  • Nginx のリバースプロキシ設定
  • Let’s Encrypt で SSL
  • 権限調整
  • 動作確認

新しいサービスを作るたびにこれを繰り返すのは非効率すぎる。

そこで今回、「サービス名」と「ポート番号」を指定するだけで、
Flask サービスの本番環境が 1 コマンドで自動構築される仕組み
を作った。

実際には次のように実行するだけで完了する。

sudo create_flask_site.sh newservice 9999

この記事では、この仕組みの構成と実際に使ってみた結果をまとめる。

なぜ自動化したのか

個人開発とはいえ、作っているサービスが増えてくると、とにかく「新しいサービスをすぐ試したい」欲求が強い。
毎回手動でインフラを組むと、作業時間とミスのリスクが積み重なっていく

「アイデアが湧いた瞬間に、すぐ本番環境で動かしたい」
この思想に沿うには、プロビジョニング自体を自分用に最適化したツール化が必要だった。


構成の全体像

採用した構成はシンプルで、個人開発向けに最適化している。

  • OS:Ubuntu(ConoHa VPS)
  • Webアプリ:Flask
  • WSGIサーバ:Gunicorn
  • プロセス管理:systemd
  • リバースプロキシ:Nginx
  • SSL:Let’s Encrypt(certbot)
  • ディレクトリ構成:/srv/app/<service_name>
  • 実行ユーザー:www-data

各サービスは次のように独立した構造になっている。

/srv/app/
  ├── Service1/
  ├── Service2/
  ├── Service3/
  │    ├── venv/
  │    ├── app.py
  │    └── ...

1 VPS で複数サービスを同時に動かす前提で設計している。


create_flask_site.sh が自動でやっていること

1コマンドで、以下のすべてを自動実行する。


① サービス用ディレクトリの作成

/srv/app/<service_name>

※権限はすべて www-data に変更しています


② Python 仮想環境の作成 + Flask / Gunicorn インストール

python3 -m venv venv
source venv/bin/activate
pip install flask gunicorn

※pythonは事前にインストールをおねがいします


③ 最小構成の app.py を自動生成

動作確認用として、下記のような簡易アプリを生成する。

from flask import Flask
app = Flask(__name__)

@app.route("/")
def index():
    return "Hello from <service_name>"

④ systemd ユニットファイルの生成 + 有効化

例:/etc/systemd/system/service1.service

[Unit]
Description=service1 Gunicorn Service
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/srv/app/service1
Environment="PATH=/srv/app/service1/venv/bin"
ExecStart=/srv/app/service1/venv/bin/gunicorn -b 127.0.0.1:8003 --timeout 300 --workers 3 app:app

[Install]
WantedBy=multi-user.target

サービス起動:

systemctl enable service1
systemctl start service1

⑤ Nginx のリバースプロキシ設定生成

/etc/nginx/sites-available/service1.domain.com

server {
    server_name service1.domain.com;

    location / {
        proxy_pass http://127.0.0.1:9999;
        proxy_set_header Host $host;
    }
}

有効化して reload:

nginx -t
systemctl reload nginx

⑥ DNS が生きていれば SSL を自動発行

スクリプトがユーザーに聞く:

今すぐ SSL(Let's Encrypt)を設定しますか? (yes/no)

yes を選べば certbot が実行される:

sudo certbot --nginx -d service1.domain.com

DNS が未反映なら失敗するが、その場合は
「後で手動で実行すべきコマンド」を表示してくれる。


メリット

  • 新規サービスの立ち上げ時間が 30分 → 5秒 に短縮
  • 手作業ミスがゼロになる
  • VPS の複数サービス管理が大幅に楽
  • 気軽に新サービスを試せる
  • ローカルで作ったものをすぐ本番に出せる
  • “個人開発の回転速度” が上がる

これは間違いなく開発効率をあげる仕組みになった。

Shellのコード全文です。よかったら使ってください

#!/usr/bin/env bash
set -e

#======================================================================
# 使い方:
#   sudo create_flask_site.sh <service_name> <port>
# 例:
#   sudo create_flask_site.sh flashlog 8002
#
# 要件:
#   - 作成パス: /srv/app/<service_name>
#   - 実行ユーザ: www-data
#   - systemd + gunicorn + nginx 自動生成
#======================================================================

if [ "$EUID" -ne 0 ]; then
  echo "root権限で実行してください(sudo create_flask_site.sh ...)"
  exit 1
fi

SERVICE="$1"
PORT="$2"

if [ -z "$SERVICE" ] || [ -z "$PORT" ]; then
  echo "使い方: $0 <service_name> <port>"
  exit 1
fi

APP_DIR="/srv/app/${SERVICE}"
DOMAIN="${SERVICE}.ideaworks.tech"

echo "=== サービス作成: $SERVICE ==="
echo "APP_DIR: $APP_DIR"
echo "PORT   : $PORT"
echo "DOMAIN : $DOMAIN"
echo

#---------------------------------------------------------
# 1) ディレクトリ作成
#---------------------------------------------------------
if [ -d "$APP_DIR" ]; then
  echo "ディレクトリ ${APP_DIR} は既に存在します。中断。"
  exit 1
fi

mkdir -p "$APP_DIR"
chown -R www-data:www-data "$APP_DIR"
cd "$APP_DIR"

#---------------------------------------------------------
# 2) 仮想環境作成 & Flask + Gunicorn インストール
#---------------------------------------------------------
sudo -u www-data python3 -m venv venv
sudo -u www-data bash -c "
  source venv/bin/activate &&
  pip install --upgrade pip &&
  pip install flask gunicorn
"

#---------------------------------------------------------
# 3) app.py 雛形作成
#---------------------------------------------------------
cat > app.py <<EOF
from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "Hello from ${SERVICE}!"

if __name__ == "__main__":
    app.run(host="127.0.0.1", port=${PORT}, debug=True)
EOF

chown www-data:www-data app.py

#---------------------------------------------------------
# 4) systemd ユニットファイル作成
#---------------------------------------------------------
SERVICE_FILE="/etc/systemd/system/${SERVICE}.service"

cat > "${SERVICE_FILE}" <<EOF
[Unit]
Description=${SERVICE} Gunicorn Service
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=${APP_DIR}
Environment="PATH=${APP_DIR}/venv/bin"
ExecStart=${APP_DIR}/venv/bin/gunicorn -b 127.0.0.1:${PORT} --timeout 300 --workers 3 app:app

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable "${SERVICE}"
systemctl restart "${SERVICE}"

echo "=== systemd 起動済み ==="
systemctl status "${SERVICE}" --no-pager

#---------------------------------------------------------
# 5) Nginx config 作成
#---------------------------------------------------------
NGINX_CONF="/etc/nginx/sites-available/${DOMAIN}"

cat > "${NGINX_CONF}" <<EOF
server {
    listen 80;
    server_name ${DOMAIN};

    location / {
        proxy_pass http://127.0.0.1:${PORT};
        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;
    }
}
EOF

ln -s "${NGINX_CONF}" /etc/nginx/sites-enabled/ || true

nginx -t
systemctl reload nginx

echo
echo "=========================================================="
echo "  構築完了! 次の作業:"
echo
echo "【必須】DNS に以下を登録してください:"
echo "  Aレコード: ${DOMAIN} → <VPSのIP>"
echo
echo "DNS反映後に SSL 有効化:"
echo "  sudo certbot --nginx -d ${DOMAIN}"
echo
echo "=========================================================="


#==========================================================
# 6) SSL 設定(ユーザー確認つき)
#==========================================================
echo
echo "=========================================================="
echo "DNS が反映されている場合、SSL を有効化できます。"
echo "今すぐ SSL(Let's Encrypt)を設定しますか? (yes/no)"
echo "=========================================================="
read SSLANSWER

if [ "$SSLANSWER" = "yes" ]; then
    echo "=== SSL設定を開始します ==="
    if certbot --nginx -d ${DOMAIN}; then
        echo "=== SSL設定が完了しました ==="
    else
        echo "=== SSL設定に失敗しました ==="
        echo "DNS がまだ反映されていない可能性があります。"
        echo
        echo "後で手動で実行してください:"
        echo "  sudo certbot --nginx -d ${DOMAIN}"
    fi
else
    echo "SSL設定はスキップされました。後で以下を実行してください:"
    echo "  sudo certbot --nginx -d ${DOMAIN}"
fi

まとめ

今回作った create_flask_site.sh によって、
Flask の本番構築が 1 コマンドで完了する環境が整った。

個人開発で複数サービスを並行して作る場合、
これは生産性を上げる武器になる。

今後も開発・運用で得た知見をこのブログにまとめていく予定です。

タイトルとURLをコピーしました