研究のデモシステムでStreamlitを使ったデモをする機会が多かったです。これまではngrokを使っていたのですが、制限が多く実験中に困ることがありました。そんな中、Cloudflare Quick Tunnelsで代替できそうだったので実際に試してみました。
結論から言うと、アカウント不要で即使えるのは想像以上に便利ですね。ただし、万能ではないので、その辺りも書いておこうと思います。
なぜngrokから移行を検討したのか
Streamlitのシステムデモをするとき、ngrok(無料版)にはいくつか困る点があった。
- 転送量・リクエスト数などの上限に当たりやすい
- 同時接続数が制限されている
WSL環境で設定する
Windowsの導入は比較的ドキュメントが多いようだったので、今回は以下の環境で試してみました。
- Windows 11
- WSL2 (Ubuntu)
- Python 3.12

1. cloudflaredのインストール
WSLのターミナルで以下を実行します。
$ wget -q https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb $ sudo dpkg -i cloudflared-linux-amd64.deb $ cloudflared --version



特に問題なくインストールでき、バージョン情報が表示されればOKです。
2. 事前設定
事前にプロジェクトの設定を行なっておきます。
$ mkdir cloudflare-Tunnels $ cd cloudflare-Tunnels/ $ uv venv Using CPython 3.12.3 interpreter at: /usr/bin/python3 Creating virtual environment at: .venv Activate with: source .venv/bin/activate $ source .venv/bin/activate $ uv pip install streamlit pandas numpy
2. Streamlitアプリを起動
テスト用に簡単なアプリを作成します。
test_app.py
# test_app.py import streamlit as st import pandas as pd import numpy as np st.title('データ分析デモ') data = pd.DataFrame({ '日付': pd.date_range('2024-01-01', periods=100), '数値': np.random.randint(10, 100, 100) }) st.line_chart(data.set_index('日付'))
プログラムの実行
$ streamlit run test_app.py
ブラウザでhttp://localhost:8501で起動することを確認。

3. Quick Tunnelで公開
別のターミナルで以下を実行:
$ cloudflared tunnel --url http://localhost:8501
すると、こんな感じでURLが表示されます。
+--------------------------------------------------------------------------------------------+ | Your quick Tunnel has been created! Visit it at (it may take some time to be reachable): | | https://shanghai-pierce-possibility-famous.trycloudflare.com | +--------------------------------------------------------------------------------------------+

URLをブラウザで開くと、Streamlitアプリが表示された。本当にこれだけ。
以下は別回線からiPadでアクセスを行なった場合のスクリーンショット

スマホからもアクセスできるし、HTTPSで接続されているのも良い🤩
良かった点😊
1. アカウント登録が不要
これが最大のメリット。cloudflaredをインストールするだけで使える。
2. 転送量・リクエスト数などの上限にひっかかりにくい
ngrokではアクセス数の上限に引っかかることが多かったのですが、変更することで引っかかりにくくなったようです。被験者の多い実験では結構これは大きいかな。
3. WSLでそのまま使える
WSL内のlocalhostを指定するだけで動く。IPアドレスを調べたり、ポートフォワーディングを設定したりする必要がない。
Windows + WSL環境でも、Linuxと同じ感覚で使えるのは助かる。
4. URLがHTTPSでアクセスできる
生成されるURLはHTTPSでアクセスできる。セキュリティ的にも安心だし、ブラウザの警告も出ない。
5. プログラムを起動後に、トンネルが起動できる
検証するアプリを起動してから、別のターミナルでトンネルを起動できる。順番を気にせずに使えるのは便利。
困った点・注意点🥲
もちろん万能ではない。使ってみて気づいた注意点も書いておく。
1. URLが毎回変わる
これはngrok(無料版)と同じ。再起動するたびにURLが変わる。
デモ中は起動したままにしておく必要がある。「ちょっとアプリを修正して再起動」すると、URLを共有し直さないといけない。
対策: デモ前にしっかりテストして、本番は再起動しないようにする。
2. URLが覚えにくい
生成されるURLは https://形容詞-名詞-動詞-副詞.trycloudflare.com みたいな感じで、かなり長い。
口頭で伝えるのは難しいので、Slackやメールで送るか、QRコード化するのが良さそう。
ということで、補助用のラッパースクリプトをpythonで作ってみました。
#!/usr/bin/env python3 """ cloudflared_tunnel.py - Cloudflare Quick Tunnelを起動してURLを取得・保存するツール 使い方: python cloudflared_tunnel.py --url http://localhost:8501 必要なライブラリのインストール: pip install qrcode pillow """ import argparse import subprocess import re import sys import time import os import signal from pathlib import Path try: import qrcode except ImportError: print("エラー: qrcodeライブラリがインストールされていません。") print("以下のコマンドでインストールしてください:") print(" pip install qrcode pillow") sys.exit(1) # グローバル変数 cloudflared_process = None def signal_handler(sig, frame): """シグナルハンドラ(Ctrl+C)""" print("\n") print("🛑 トンネルを停止しています...") if cloudflared_process: cloudflared_process.terminate() cloudflared_process.wait(timeout=5) print("✅ トンネルを停止しました") sys.exit(0) def parse_arguments(): """コマンドライン引数を解析""" parser = argparse.ArgumentParser( description='Cloudflare Quick Tunnelを起動してURLを取得・保存します', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" 使用例: %(prog)s --url http://localhost:8501 %(prog)s --url http://localhost:5000 --output-dir ./output 出力ファイル: URL.txt - 生成されたトンネルのURL QR.png - URLのQRコード画像 停止方法: Enter キーを押す、または Ctrl+C """ ) parser.add_argument( '--url', required=True, help='公開するローカルアプリのURL (例: http://localhost:8501)' ) parser.add_argument( '--output-dir', default='.', help='出力ディレクトリ (デフォルト: カレントディレクトリ)' ) parser.add_argument( '--url-file', default='URL.txt', help='URLを保存するファイル名 (デフォルト: URL.txt)' ) parser.add_argument( '--qr-file', default='QR.png', help='QRコード画像のファイル名 (デフォルト: QR.png)' ) return parser.parse_args() def extract_tunnel_url(output_line): """cloudflaredの出力からトンネルURLを抽出""" # https://xxxxx.trycloudflare.com の形式のURLを探す url_pattern = r'https://[a-z0-9\-]+\.trycloudflare\.com' match = re.search(url_pattern, output_line) if match: return match.group(0) return None def start_cloudflared_tunnel(local_url): """cloudflared tunnelを起動してURLを取得""" global cloudflared_process print(f"cloudflared tunnel を起動中...") print(f"ローカルURL: {local_url}") print("トンネルURLの取得を待機中...\n") try: # cloudflaredを起動 cloudflared_process = subprocess.Popen( ['cloudflared', 'tunnel', '--url', local_url], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1 ) # 出力を読み取ってURLを探す tunnel_url = None timeout = 30 # 30秒タイムアウト start_time = time.time() for line in cloudflared_process.stdout: print(line.rstrip()) # 出力を表示 # URLを抽出 url = extract_tunnel_url(line) if url: tunnel_url = url print(f"\n✅ トンネルURL取得成功: {tunnel_url}\n") break # タイムアウトチェック if time.time() - start_time > timeout: print(f"\n⚠️ タイムアウト: {timeout}秒以内にURLを取得できませんでした") cloudflared_process.terminate() return None if not tunnel_url: print("\n⚠️ トンネルURLを取得できませんでした") cloudflared_process.terminate() return None print("📝 cloudflaredプロセスが起動しました") print(f" プロセスID: {cloudflared_process.pid}\n") return tunnel_url except FileNotFoundError: print("エラー: cloudflaredコマンドが見つかりません") print("cloudflaredがインストールされていることを確認してください") print("インストール方法: https://github.com/cloudflare/cloudflared") return None except Exception as e: print(f"エラー: cloudflaredの起動に失敗しました - {e}") return None def save_url_to_file(url, file_path): """URLをテキストファイルに保存""" try: with open(file_path, 'w', encoding='utf-8') as f: f.write(url + '\n') print(f"✅ URLをファイルに保存しました: {file_path}") return True except Exception as e: print(f"⚠️ URLファイルの保存に失敗しました: {e}") return False def generate_qr_code(url, file_path): """URLからQRコードを生成して画像ファイルに保存""" try: # QRコードを生成 qr = qrcode.QRCode( version=1, # 自動調整 error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4, ) qr.add_data(url) qr.make(fit=True) # 画像を生成 img = qr.make_image(fill_color="black", back_color="white") # ファイルに保存 img.save(file_path) print(f"✅ QRコードを生成しました: {file_path}") return True except Exception as e: print(f"⚠️ QRコードの生成に失敗しました: {e}") return False def main(): """メイン処理""" # シグナルハンドラを登録 signal.signal(signal.SIGINT, signal_handler) args = parse_arguments() # 出力ディレクトリの作成 output_dir = Path(args.output_dir) output_dir.mkdir(parents=True, exist_ok=True) # 出力ファイルのパス url_file_path = output_dir / args.url_file qr_file_path = output_dir / args.qr_file print("=" * 60) print("Cloudflare Quick Tunnel URLキャプチャツール") print("=" * 60) print() # cloudflared tunnelを起動してURLを取得 tunnel_url = start_cloudflared_tunnel(args.url) if not tunnel_url: print("\n❌ トンネルURLの取得に失敗しました") sys.exit(1) # URLをファイルに保存 save_url_to_file(tunnel_url, url_file_path) # QRコードを生成 generate_qr_code(tunnel_url, qr_file_path) print() print("=" * 60) print("✅ 処理完了") print("=" * 60) print(f"トンネルURL: {tunnel_url}") print(f"URLファイル: {url_file_path}") print(f"QRコード: {qr_file_path}") print() print("💡 このURLを共有してアクセスしてもらってください") print("=" * 60) print() print("📌 トンネルは実行中です") print(f" プロセスID: {cloudflared_process.pid}") print() print("⏸️ 停止するには Enter キーを押してください") print(" (または Ctrl+C)") print() try: # キー入力待ち input() except KeyboardInterrupt: # Ctrl+Cの場合はシグナルハンドラが処理 pass # トンネルを停止 print("\n🛑 トンネルを停止しています...") if cloudflared_process: cloudflared_process.terminate() try: cloudflared_process.wait(timeout=5) print("✅ トンネルを停止しました") except subprocess.TimeoutExpired: cloudflared_process.kill() print("✅ トンネルを強制停止しました") print("=" * 60) if __name__ == '__main__': main()
プログラムの実行
$ python cloudflared_tunnel.py --url http://localhost:8501


3. アクセス制限はかけられない
URLを知っていれば誰でもアクセスできる。手軽さの裏返しで、セキュリティ面では注意が必要。 短時間のデモなら問題ないが、機密情報を扱う場合は注意が必要。アプリ側で認証を実装するか、別の方法(永続トンネル + Cloudflare Access)を検討する必要がある。
今回の実験で気付いたこと 複数のトンネルを起動が可能!
最初、「1つのトンネルで複数のアプリを公開できないのか?」と思っていたけど、別のターミナルで別のトンネルを起動すればOKだった。つまり、複数のアプリを同時に公開できる。
# ターミナル1 $ streamlit run app1.py & $ cloudflared tunnel --url http://localhost:8501 # ターミナル2 $ python -m http.server 8000 & $ cloudflared tunnel --url http://localhost:8000
それぞれ異なるURLが生成される。当たり前のことだが、最初気づかなかったけど、複数のアプリを同時に公開できるのは便利。
おわりに
Cloudflare Quick Tunnelsを実際に使ってみた感想としては以下のようになるでしょうか。
良かったこと
- 時間制限がなく、安心してデモできる
- WSL環境でもそのまま使える
- 複数のトンネルを同時に起動できる
- HTTPSでアクセスできる
注意すること
- URLが毎回変わる(再起動で変わる)
- アクセス制限はかけられない
- URLが長くて覚えにくいかも
向いている用途
- 授業・ワークショップでのデモ
- 一時的なプレビュー共有
- PoCシステムの発表
向いていない用途
- 長期運用(固定URLが必要)
- 機密情報を扱うシステム(認証が必要)
個人的にはngrokの「面倒な部分」(アカウント登録、2時間制限)を解消してくれるサービスといえるかな。 デモで「ちょっと見せたい」には最適。ただし、本格的な運用には向かないので、用途に応じて使い分けるのが良さそう。
もし、固定URLが必要になったら、cloudflareにある永続トンネル(ドメイン必要)を検討するのも良いかも。 ただ、ドメインが必要なので、ドメインの管理コストも必要です。