問題
Railway にデプロイしたい。でも git push が通らない。
$ git push origin master
...
error: RPC failed; curl 28 Operation too slow.
Less than 1000 bytes/sec transferred the last 10 seconds
send-pack: unexpected disconnect while reading sideband packet
fatal: the remote end hung up unexpectedly
リポジトリが巨大すぎて HTTPS push がタイムアウトする。
なぜそうなったか
もともと1つのモノリシックなリポジトリで開発していた。途中でMLモデル、HTMLレポート、大量のJSONログが混入し、気づいたら数GBに膨れ上がっていた。
.gitignore を直せばよかったが、過去のコミットにすでに含まれているので git のオブジェクトサイズは変わらない。
やりたいのは「main.py だけを Railway のデプロイ用リポジトリに反映させること」。
解決策:GitHub Contents API でファイルを直接更新
GitHub には REST API でファイルを1つだけ更新できるエンドポイントがある。
PUT /repos/{owner}/{repo}/contents/{path}
git push は不要。HTTPS でファイルの内容を送るだけ。
手順
1. 現在の SHA を取得(更新に必須)
GitHub API でファイルを更新するには、現在のファイルの sha が必要。
TOKEN="ghp_xxxxxxxxxxxxxxxxxx"
OWNER="your_username"
REPO="your-deploy-repo"
FILE="main.py"
SHA=$(curl -s \
-H "Authorization: token $TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/contents/$FILE" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['sha'])")
echo "SHA: $SHA"
2. ファイルを Base64 エンコード
CONTENT=$(base64 -w 0 main.py)
3. PUT でアップロード
curl -s -X PUT \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/$OWNER/$REPO/contents/$FILE" \
-d "{
\"message\": \"Update $FILE via API\",
\"content\": \"$CONTENT\",
\"sha\": \"$SHA\"
}" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('commit',{}).get('sha','ERROR'))"
コミット SHA が返ってきたら成功。Railway は GitHub の変更を検知して自動デプロイされる。
Python 版(スクリプトとして保存しておくと便利)
import os, base64, json, urllib.request
TOKEN = os.environ["GITHUB_TOKEN"]
OWNER = "your_username"
REPO = "your-deploy-repo"
FILE = "main.py"
def get_sha(file_path: str) -> str:
url = f"https://api.github.com/repos/{OWNER}/{REPO}/contents/{file_path}"
req = urllib.request.Request(url, headers={"Authorization": f"token {TOKEN}"})
with urllib.request.urlopen(req) as res:
return json.loads(res.read())["sha"]
def upload_file(local_path: str, remote_path: str, message: str):
sha = get_sha(remote_path)
with open(local_path, "rb") as f:
content = base64.b64encode(f.read()).decode()
payload = json.dumps({
"message": message,
"content": content,
"sha": sha,
}).encode()
url = f"https://api.github.com/repos/{OWNER}/{REPO}/contents/{remote_path}"
req = urllib.request.Request(
url, data=payload, method="PUT",
headers={
"Authorization": f"token {TOKEN}",
"Content-Type": "application/json",
}
)
with urllib.request.urlopen(req) as res:
result = json.loads(res.read())
print(f"✓ commit: {result['commit']['sha'][:7]}")
upload_file("main.py", "main.py", "Deploy: update main.py")
実行:
GITHUB_TOKEN=ghp_xxx python3 deploy.py
# ✓ commit: a3f9c12
この方法の制約
| 項目 | 内容 |
|---|---|
| ファイルサイズ | 1ファイル最大 100MB(Base64後) |
| 更新単位 | 1ファイルずつ(複数ファイルはAPIを複数回呼ぶ) |
| コミット | 1ファイルにつき1コミット |
| ブランチ | デフォルトブランチ以外も指定可(branch パラメータ追加) |
複数ファイルを1コミットにまとめたい場合は Git Data API(Tree API)を使う必要がある(かなり複雑になる)。
本質的な解決策(参考)
このアプローチは「対症療法」。本質的には:
-
git filter-repoで大きなファイルを履歴ごと削除 - 大きなバイナリは Git LFS に移す
- 最初からデプロイ専用リポジトリを分ける
ただし「今すぐ1ファイル更新したいだけ」というときは GitHub API が一番手っ取り早い。
まとめ
- 巨大リポジトリからの
git pushはタイムアウトする - GitHub Contents API (
PUT /repos/.../contents/...) でファイル単体を更新できる - SHA の取得 → Base64 エンコード → PUT の3ステップ
- Railway は GitHub push をトリガーに自動デプロイするので、これで十分動く
🚀 関連プロジェクト
この記事は NOVE OS 開発中に得た知見をまとめたものです。
NOVE OS は Rocky Linux サーバーを 1コマンドで自動最適化するツールです。eBPF・Rust・量子計算など最先端技術を統合し、ベンチマークスコア 2749点/3000点(91.6%) を達成しています。
👉 NOVE OS 公式サイト - 無料トライアルあり