研究のデモシステムで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
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で作ってみました。
"""
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を抽出"""
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_process = subprocess.Popen(
['cloudflared', 'tunnel', '--url', local_url],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
bufsize=1
)
tunnel_url = None
timeout = 30
start_time = time.time()
for line in cloudflared_process.stdout:
print(line.rstrip())
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 = 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()
tunnel_url = start_cloudflared_tunnel(args.url)
if not tunnel_url:
print("\n❌ トンネルURLの取得に失敗しました")
sys.exit(1)
save_url_to_file(tunnel_url, url_file_path)
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:
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にある永続トンネル(ドメイン必要)を検討するのも良いかも。
ただ、ドメインが必要なので、ドメインの管理コストも必要です。
参考リンク
github.com
developers.cloudflare.com