デプロイでまたハマった話
— CRLF・バンドル・static

「ボタン1つデプロイの記録」で書いたように、さくらサーバーへはボタン1つで更新を反映するアプリで運用しています。ところが、昨日は問題なく動いていたのに、次の日にまたエラーが続くことがありました。原因はデプロイ手順そのものの破損ではなく、スクリプトの渡し方コード・バンドルの不一致、そして静的ファイルの入れ忘れの3点でした。同じように「動いていたのにまた失敗する」と感じたときの参考に、原因と対処、さらに未然に防ぐ設定までまとめます。

1. 何が起きたか(3つの現象)

デプロイを実行すると、次のようなエラーが出るようになりました。

  • 3/3 の直後bash: line 36: $'\r': command not foundbash: line 5: m: command not foundrmm になっている)で終了。
  • スクリプトは最後まで流れたが:「HTTP code: 000」「App not responding」と表示され、サイトにアクセスしても応答がない。
  • デプロイは成功したが:サイドバーのアバター画像(/static/images/avatar-person-blue.png)が 404 で表示されない。

いずれも「昨日動いていたデプロイ」が急に壊れたのではなく、環境の差や、アプリ側の変更にバンドル側の更新が追いついていなかったことが原因でした。

2. 原因と対処の整理

現象原因対処
CRLF / $'\r' / m: command not found Windows で編集した deploy_remote.shCRLF(\r\n)のままサーバーに渡り、bash が行末の \r をコマンドと解釈してエラーに。対策で tr -d '\r' を通したところ、リモート側で「文字 r」まで削られ、rmm になった。 スクリプトをパイプで渡さない。内容を CR 除去して 一時ファイルに書き出し → scp で送る → ssh で bash ファイル を実行する方式に変更。
HTTP code: 000 / App not responding app.pyoffers_storageoffer_inquiries_storage を import するようになったのに、build_deploy_bundle.ps1$rootFiles にその2つの .py が入っていなかった。サーバーにモジュールが届かず、Gunicorn が ModuleNotFoundError で落ち、5000 番で何も listen していないため curl が 000 に。 build_deploy_bundle.ps1$rootFilesoffers_storage.pyoffer_inquiries_storage.py を追加。app.py で新しく import するローカルモジュールを増やしたら、必ずバンドルに追加する習慣にする。
アバター画像 404 テンプレートで static/images/avatar-person-blue.png を参照しているのに、(1) バンドルに static/ フォルダをそもそも含めていなかった、(2) プロジェクト内にその画像ファイルが存在していなかった。 バンドルに static/ のコピーを追加し、static/images/avatar-person-blue.png を用意。

3. CRLF を未然に防ぐ設定

「スクリプトの渡し方」は一時ファイル+scp で解消していますが、deploy_remote.sh が CRLF で保存・コミットされにくくするため、次の2つを入れておくことをおすすめします。

  • .gitattributes*.shdeploy_remote.shtext eol=lf を指定。Git がリポジトリ内で LF に統一し、チェックアウト時も LF で取り出します。
  • .editorconfig[*.sh]end_of_line = lf を指定。Cursor / VSCode など対応エディタで .sh を保存するときに LF になります。

既存のデプロイ手順(一時ファイル+CR 除去+scp)はそのまま残し、二重の対策として運用すると安心です。

4. まとめ

「昨日は動いていたのにまたエラーになった」とき、原因は (1) スクリプトの渡し方(CRLF・tr の誤除去)、(2) app.py に追加した import に対応する .py をバンドルに入れ忘れた、(3) 参照している静的ファイルをバンドルに含めていなかった、のいずれかであることが多いです。デプロイ手順そのものを疑うより、まず「バンドルに必要なファイルが全部入っているか」「Windows から Linux に渡すスクリプトの改行は大丈夫か」を確認すると、切り分けがしやすくなります。

ボタン1つデプロイの記録」では、デプロイアプリの導入と、それまでに起きたトラブル(data 退避・ポート占有・CRLF・sudo・ランチャー表示など)をまとめています。ここでは、その後の「またハマった」3点と、未然に防ぐ設定を補足として書きました。

← 立ち上げストーリー
← プログラム構築の記録
← デプロイの記録
← ボタン1つデプロイの記録
← ログイン設定の記録
← 改善記録
← ファイル紹介の使い方
← OGP・SEOの記録
← 統合ログインの設計・経緯
← Search Console・サイトマップ
← 環境変数・.env の管理
← Git 入門・インストール
← インストール後に Git で行う設定
← カード神経衰弱の記録
← 異世界シューティングの記録
← 異世界シューティングの難易度
← 異世界戦記(全画面・迷路レイアウト)の記録
← 複数人でのゲーム進行
← 異世界衰弱(不具合の修正)
← 異世界衰弱(機能別フローチャート)
← 異世界ポイントの活用について
← 異世界ポイント市場の実装記録