個人開発・サービス運用

Cloudflare R2による個人開発アプリのDBバックアップ自動化とコスト削減

個人開発サービスの障害対応で最初に確認すべきものは、アプリケーションのログではない。復元可能なバックアップが存在するかである。…

Cloudflare R2による個人開発アプリのDBバックアップ自動化とコスト削減

サーバー障害、誤ったマイグレーション、管理者アカウントの侵害、ストレージ破損。原因は異なるが、復旧時に必要なのは同じである。直近のデータベースと、必要なファイルを戻せる保存先である。

しかし、バックアップの保存先をクラウドストレージにすると、別の問題が発生する。バックアップの作成より、復元時のデータ転送量がコストを押し上げるケースである。特にサービス運用の初期段階では、売上よりもサーバー費用の比率が高い。バックアップのためだけに複数の有料サービスを契約する構成は避けたい。

Cloudflare R2は、この問題に対する有力な選択肢である。毎月10GBのストレージ、100万回のClass A操作、1,000万回のClass B操作を無料で利用できる。エグレス、つまりデータ転送の料金も無料である。

ただし、R2は無条件に無料の保存先ではない。無料枠を超えれば従量課金が発生する。S3互換であるが、AWS S3と同じ設定をそのまま適用できるわけでもない。個人開発で使うには、料金体系と互換性の境界を理解したうえで構成する必要がある。

個人開発のバックアップ先としてCloudflare R2が選ばれる理由

無料枠よりも、エグレス無料の意味が大きい

R2の特徴として、10GBの無料ストレージ枠がよく挙げられる。個人開発のデータベースバックアップであれば、この容量だけでも一定期間は運用できる。

ただし、実際の運用で効くのはエグレス無料である。

バックアップは保存するだけでは完結しない。障害が起きたときにダウンロードする。検証環境へコピーする。月次でアーカイブを取得する。別のストレージへ移行する。復元テストを行う場合も、保存済みデータを読み出す必要がある。

AWS S3では、データ転送に1GBあたり約0.09ドルが発生するケースがある。数GBのバックアップを一度取得するだけなら大きな金額ではない。しかし、個人開発では次のような操作が重なる。

  • 本番環境から手元の開発環境へバックアップを取得する
  • ステージング環境へ同じバックアップを展開する
  • 障害調査用に複数世代のバックアップをダウンロードする
  • 移行前のデータを別の環境で検証する
  • 復元手順のテストを定期的に実行する

この転送が繰り返されると、保存容量よりもデータ転送費のほうが問題になる。R2ではエグレス料金が無料であるため、復元や検証の回数をコスト面で抑えやすい。

個人開発のバックアップでは、保存料金よりも「戻すときの料金」と「戻せるかどうか」がボトルネックになる。

R2の料金構造

無料枠と有料単価は次のとおりである。

項目無料枠無料枠超過後の単価
Standardストレージ10GB/月1GBあたり月額0.015ドル
Class A操作100万回/月100万回あたり4.50ドル
Class B操作1,000万回/月100万回あたり0.36ドル
エグレス無料0ドル
Infrequent Accessストレージ別体系1GBあたり月額0.01ドル

Class Aには書き込みやリスト操作が含まれる。Class Bは読み取り操作である。バックアップ用途では、通常は大量の書き込みと少数の読み取りが発生する。

例えば、毎日1回、1つのバックアップファイルをアップロードするだけなら、月間の書き込み回数はおよそ30回である。100万回の無料枠とは比較にならない。多数の小さなファイルを頻繁に同期する構成でなければ、操作回数が課金の中心になる可能性は低い。

一方で、アプリケーションのログ、画像、キャッシュ、エクスポートファイルをすべてR2へ保存し、細かいファイルを大量に更新する構成では話が変わる。バックアップだけを置くバケットと、ユーザー向けのオブジェクト保存先は分けたほうがよい。

バックアップ専用バケットにする

R2をバックアップ専用にする場合、公開配信用のストレージとして使う必要はない。むしろ、公開設定を避けるべきである。

保存する対象は、例えば次のように整理する。

  • データベースのダンプファイル
  • Laravelのアプリケーション設定に必要な暗号化済みバックアップ
  • WordPressのデータベースとアップロードファイル
  • 復元手順の検証用アーカイブ
  • バックアップのメタデータとハッシュ値

画像配信や静的ファイル配信と同じバケットにすると、権限設計とライフサイクル管理が複雑になる。バックアップ削除のルールが、ユーザー向けファイルの保存期間に影響する可能性もある。

個人開発では、サービスの規模よりも運用担当者の人数が少ないことがリスクになる。設定を単純化できる境界で、バケットを分離しておくほうが事故を減らせる。

LaravelアプリのバックアップをR2へ転送する

Laravelでは、spatie/laravel-backupと、S3互換のファイルシステムドライバーを組み合わせる構成が扱いやすい。

処理の流れは単純である。

1. データベースのダンプを作成する

2. アプリケーションの指定ディレクトリを圧縮する

3. バックアップファイルを一時保存する

4. R2へアップロードする

5. 古い世代を削除する

6. 成否をログへ記録する

ここで重要なのは、バックアップの作成と保存先への転送を別の処理として確認することである。データベースのダンプに成功しても、R2へのアップロードに失敗していれば復元可能なバックアップは存在しない。

R2用の認証情報を分離する

R2のアクセスキーは、通常のアプリケーション用キーとは分ける。アプリケーションが侵害された場合に、バックアップを削除されるリスクを下げるためである。

最低限、次の情報を環境変数として管理する。

  • R2のアクセスキーID
  • R2のシークレットアクセスキー
  • アカウントに紐づくR2エンドポイント
  • バケット名
  • バックアップ保存用のパス
  • 使用するリージョン情報

アクセスキーを.envへ直接記述する場合も、リポジトリへコミットしてはいけない。Docker環境では、環境変数、シークレット管理機能、デプロイ基盤の秘密情報ストアのいずれかを使う。

R2のエンドポイントは、Cloudflareの管理画面で表示されるS3互換エンドポイントを使用する。AWSの標準エンドポイントを指定してはいけない。バケット名だけを変更しても、R2へ接続できるわけではない。

Laravelのディスク設定で起きるACLエラー

R2とLaravelを接続するとき、最も頻繁に発生するのが公開範囲の設定に関するエラーである。

AWS S3向けの設定では、ファイルの公開範囲をpublicにする構成がよく使われる。しかし、Cloudflare R2はオブジェクト単位のACLヘッダーに対応していない。LaravelのFlysystem設定でvisibilitypublicを指定すると、アップロード時にACL関連のエラーが発生する。

バックアップ保存先では、公開設定は不要である。ディスクの可視性はprivateを前提にする。

設定の確認箇所は次のとおりである。

  • ファイルシステムのディスク設定で公開可視性を指定していないか
  • バックアップパッケージ側で公開用のオプションを有効にしていないか
  • アップロード処理がACLヘッダーを送信していないか
  • S3互換ドライバーのエンドポイントがR2を指しているか
  • バケット名と保存パスが一致しているか

この問題は、認証情報の誤りとは異なる。キーが正しくても、ACLの扱いがR2に合わなければ失敗する。エラーログに認証失敗が出ていない場合は、公開範囲の設定から確認するほうが早い。

R2はS3互換である。ただし、S3の設定ファイルを完全互換で受け入れるサービスではない。互換性の対象と対象外を分けて考える必要がある。

バックアップ世代管理をR2側だけに任せない

バックアップの保存期間は、パッケージ側で管理する。R2にファイルを置くだけでは、古いバックアップが無期限に蓄積する。

例えば、次のような保持方針が考えられる。

  • 日次バックアップは直近7世代
  • 週次バックアップは直近4世代
  • 月次バックアップは直近12世代
  • リリース前の手動バックアップは削除対象外
  • 復元検証に使うバックアップは検証完了まで保持

世代数を増やすほど復元可能な時点は増える。しかし、ストレージ容量も増える。毎日データベースを丸ごと保存する方式で、1ファイルあたり500MBなら、30日で15GBになる。10GBの無料枠は超過する。

この場合は、次の設計を比較する。

方式長所短所
毎回フルバックアップ復元手順が単純保存容量が増えやすい
日次フル、週次アーカイブ運用と復元のバランスがよい保持ルールが増える
フル+差分保存容量を抑えやすい復元処理と依存関係が複雑
リアルタイムレプリケーション障害直前まで戻しやすい誤操作も同期される
外部サービスへの多重保存障害耐性が高いコストと管理対象が増える

個人開発の初期段階では、日次フルバックアップを7世代程度保持する構成から始めるのが現実的である。データ量が増えた段階で差分やレプリケーションを検討すればよい。

自動処理は成功通知より失敗通知を重視する

バックアップ処理が毎日成功している場合、通知を送らない設計でも運用できる。問題は失敗したときである。

確認すべき情報は、単なる終了コードではない。

  • ダンプファイルが生成されたか
  • ファイルサイズが前回と比較して極端に小さくないか
  • R2へのアップロードが完了したか
  • R2上のオブジェクトが読み取れるか
  • 保持世代の削除が意図どおり実行されたか
  • 最終成功時刻が監視対象に記録されているか

データベースのダンプが空ファイルになっても、コマンド自体は終了コード0を返すことがある。アップロード成功だけを監視していると、壊れたバックアップを正常と誤認する。

最低限、バックアップファイルのサイズとハッシュ値を記録する。前回比で急激な変化があった場合は、処理を成功扱いにしないほうがよい。

SQLite環境ではLitestreamを使う

SQLiteを使う個人開発アプリでは、ダンプファイルを毎日作る方式よりも、Litestreamによるレプリケーションが適している場合がある。

SQLiteは単一ファイルでデータベースを構成できる。小規模なサービスでは、PostgreSQLやMySQLより運用負荷が低い。一方で、ファイルそのものを単純にコピーすると、書き込み中の状態やWALの扱いによって整合性に問題が出る。

LitestreamはSQLiteの変更を監視し、R2などのオブジェクトストレージへレプリケーションする。一定間隔でのフルコピーではなく、データベースの変更を継続的に転送する方式である。

Litestreamの利点と限界

Litestreamの利点は、復旧時点を細かく設定しやすいことである。日次バックアップでは、最大24時間分のデータを失う可能性がある。レプリケーションを常時実行していれば、より直近の状態へ戻せる。

ただし、リアルタイム同期はバックアップと同じではない。

誤って大量のレコードを削除した場合、その変更もR2へ同期される。アプリケーションの不具合でデータが破壊された場合も、破壊後の状態がレプリケーションされる。したがって、レプリケーションに加えて、定期的なスナップショットや世代管理が必要になる。

整理すると、役割は次のように分かれる。

  • Litestream:障害直前に近い状態への復旧
  • 定期バックアップ:誤操作や論理障害の前の状態への復旧
  • 手動バックアップ:マイグレーションや大規模更新前の保険

この3つを同じ目的として扱うと、復旧方針を決められない。

R2の同時実行制限への対応

LitestreamでR2を利用する場合、同時アップロード数が問題になることがある。Litestreamのバージョン0.5.8以降では、R2エンドポイントに対して同時実行数が自動的に2へ設定される。

これは性能を意図的に制限しているのではない。R2側の厳格な同時アップロード制限を回避するための設定である。高速なネットワークを使っているからといって、同時実行数を無制限に増やせばよいわけではない。

SQLiteのデータ量が小さい個人アプリでは、同時実行数2でも運用上の問題は起きにくい。レプリケーション遅延が発生していないかをログで確認し、必要なら同期状態を監視する。

SQLiteで見落としやすいファイル

SQLiteのバックアップでは、メインのデータベースファイルだけを対象にすると不十分な場合がある。

WALモードを使用している場合、書き込み内容が一時的にWALファイルへ記録される。アプリケーションの停止タイミングやチェックポイントの状態によっては、データベースファイルのコピーだけでは最新の変更を含まない可能性がある。

そのため、次の点を確認する。

  • SQLiteがWALモードで動作しているか
  • Litestreamが対象データベースを正しく認識しているか
  • アプリケーションとLitestreamが同じファイルを参照しているか
  • コンテナ再起動後にレプリケーションが継続するか
  • R2から復元したデータベースを実際に開けるか

Dockerで運用する場合、SQLiteのデータベースファイルをコンテナ内部に置いてはいけない。コンテナを削除するとデータも消えるため、永続ボリュームへ配置する。Litestreamのコンテナから、そのボリュームを読み取れる構成にする必要がある。

Docker環境でPostgreSQLとMySQLを定期バックアップする

PostgreSQLやMySQLをDockerで運用している場合、データベースコンテナとバックアップ処理を分離する構成が扱いやすい。

データベースコンテナの中でCronを動かす方法もある。しかし、データベースの実行環境と定期処理の責務が混ざる。イメージ更新、権限設定、ログ管理が複雑になるため、専用のバックアップコンテナを用意するほうが保守しやすい。

構成要素は次の4つである。

  • PostgreSQLまたはMySQLのデータベースコンテナ
  • ダンプを作成するバックアップコンテナ
  • R2へ転送するS3互換クライアント
  • CronまたはSystemdタイマー

転送クライアントには、AWS CLIやrcloneを使える。R2はS3互換APIを提供しているため、R2専用の転送機能を実装する必要はない。

PostgreSQLのバックアップ

PostgreSQLでは、論理バックアップにpg_dumpを使う。データベース単位で復元する場合に向いている。

バックアップ処理では、次の順序を崩さない。

1. データベースコンテナへ接続できることを確認する

2. pg_dumpで一時ファイルへ出力する

3. 圧縮する

4. ファイルサイズを確認する

5. R2へアップロードする

6. R2上のオブジェクトを確認する

7. ローカルの一時ファイルを削除する

ダンプ処理を標準出力から直接R2へ流す方法もある。しかし、途中で接続が切れた場合に、どこまで処理が完了したか確認しにくい。個人開発の運用では、一時ファイルを作成して検証してから転送するほうが障害調査は容易である。

大規模データベースでは、論理バックアップの処理時間が長くなる。サービスのトラフィックが少ない時間帯に実行し、バックアップ中のCPU使用率とディスクI/Oを計測する。バックアップ処理がアプリケーションのボトルネックになっているなら、実行頻度や圧縮方式を見直す。

MySQLのバックアップ

MySQLでは、mysqldumpが一般的である。トランザクション整合性を維持するため、InnoDBを使用している場合は適切なオプションを指定する。

特に、テーブルロックによってアプリケーションの書き込みが停止しないかを確認する。小規模なデータベースでは問題が見えにくいが、アクセス数が増えるとバックアップ処理の影響が顕在化する。

バックアップファイルの名前には、次の情報を含めると管理しやすい。

  • サービス名
  • データベース種別
  • UTCまたはJSTの実行時刻
  • バックアップ種別
  • スキーマのバージョン

例えば、同じ日付のファイルが複数存在しても、生成時刻で区別できる。タイムゾーンを運用途中で変更すると、世代管理に混乱が出るため、保存ファイル名はUTCに統一する方法が安定する。

Dockerでの秘密情報と権限

バックアップコンテナに、データベースの管理者権限とR2の書き込み権限を同時に与えることになる。この権限は強い。

R2側では、バックアップ用バケットに限定したAPIトークンを作成する。ユーザー向けファイルを保存している別バケットまで操作できるキーを使わない。

データベース側でも、バックアップ専用ユーザーを用意する。アプリケーションが利用するユーザーと、バックアップを作成するユーザーを分ける。アプリケーションのSQLインジェクションが発生した場合に、バックアップ権限まで奪われるリスクを低減できる。

ただし、バックアップ専用ユーザーに必要な権限はデータベースごとに異なる。権限を削りすぎると、特定のテーブルやビューがダンプされない。権限変更後は、実際に生成されたダンプを復元して確認する必要がある。

復元テストを自動化する

バックアップの存在確認だけでは不十分である。R2上にファイルがあっても、復元できなければ価値はない。

月1回でもよいので、次のテストを行う。

1. R2からバックアップを取得する

2. 一時的なデータベースコンテナを起動する

3. ダンプを復元する

4. テーブル数や主要テーブルの件数を確認する

5. Laravelのヘルスチェックを実行する

6. テスト完了後に一時環境を削除する

WordPressの場合は、管理画面へのログイン、記事一覧、画像表示まで確認する。データベースの復元に成功しても、アップロードファイルのパスや権限が壊れていればサービスは復旧しない。

復元テストは、バックアップ処理の一部である。バックアップ作成だけを自動化し、復元確認を手動のままにすると、障害発生時に初めて問題を知ることになる。

WordPressサイトのバックアップをR2へオフロードする

WordPressでは、データベースだけでなくファイルも対象になる。

対象となる主なファイルは、wp-content/uploadsの画像、テーマ、プラグイン、設定ファイルである。データベースには記事やユーザー情報が保存されるが、画像ファイルは通常、データベースの外にある。

そのため、WordPressのバックアップでは次の2種類を分けて考える。

  • データベースのバックアップ
  • WordPressファイルのバックアップ

Duplicator ProやR2 Cloud Storageなど、S3互換APIに対応したプラグインを使えば、R2へバックアップを保存できる。管理画面からAPIキー、エンドポイント、バケット名を指定し、スケジュールを設定する構成である。

ただし、プラグインを導入しただけでは運用は完了しない。バックアップの圧縮方式、世代数、ファイル除外、実行時間を設定する必要がある。

WordPressでファイル容量が膨らむ原因

WordPressのバックアップ容量は、記事数だけでは決まらない。次の要因で急増する。

  • 元画像と複数サイズのサムネイルが保存される
  • 画像を何度も差し替えて古いファイルが残る
  • キャッシュファイルをバックアップ対象に含める
  • プラグインがログや一時ファイルを生成する
  • ステージング環境のコピーが同じ保存先へ混ざる

特にキャッシュや一時ファイルをバックアップに含めると、復元に不要なデータで容量だけが増える。wp-content/cacheなど、再生成可能なディレクトリは対象から除外する設計が合理的である。

ただし、除外設定はプラグインや構成によって異なる。除外後にサイトが正常に復元できるかを確認する。単純に容量の大きいディレクトリを削除対象にすればよいわけではない。

WordPressのバックアップ頻度

記事の更新頻度が低いサイトで、毎時フルバックアップを実行しても費用対効果は低い。反対に、会員登録や注文データを扱うサイトでは、記事の更新頻度だけで判断できない。

目安としては、次のように分けられる。

  • 静的な企業サイト:週次バックアップ
  • 個人ブログ:日次または記事更新前の手動バックアップ
  • 会員制サービス:日次データベース、定期的なファイルバックアップ
  • 小規模な通販サイト:日次以上のデータベースバックアップと復元テスト

WordPressの管理者が複数いる場合、誰かがプラグインを更新する前に手動バックアップを作成する運用を決めておく。自動バックアップだけに依存すると、更新直前の状態が存在しないことがある。

R2運用で発生するコストと依存関係を把握する

R2を使うと、バックアップの保存コストは抑えやすい。しかし、運用コストがゼロになるわけではない。

まず、無料枠の範囲を超える可能性を計算する。データベースのサイズだけでなく、保持世代数、ファイル数、アップロード頻度、復元テストの回数を含める。

例えば、毎日500MBのフルバックアップを作成し、7世代を保持する場合、単純計算では3.5GBである。圧縮率が高ければ、さらに小さくなる。一方、WordPressの画像を含めて毎日2GB保存すると、7世代で14GBになる。10GBの無料枠を超える。

無料枠を超えた場合、Standardストレージは1GBあたり月額0.015ドルである。容量だけを見れば大きな金額ではない。しかし、サービス運用ではストレージ以外の要素もある。

  • API操作数
  • 複数リージョンへの複製
  • 外部監視サービス
  • 復元用の一時サーバー
  • バックアップ処理を実行するコンテナ
  • 長期保存用の別ストレージ

「R2に置けば無料」と考えると、見積もりを誤る。正しくは、無料枠とエグレス無料によって、個人開発のバックアップ基盤を低コストで構成しやすいサービスである。

ライフサイクル設計

バックアップを長期間保持するなら、世代管理とライフサイクルルールを組み合わせる。

日次の短期バックアップは、直近の障害復旧用である。月次の長期バックアップは、数か月前の誤操作やデータ破損から戻すために使う。すべてを同じ保存期間にすると、容量だけが増える。

構成例は次のとおりである。

  • 直近7日:日次フルバックアップ
  • 直近8週:週次バックアップ
  • 直近12か月:月次バックアップ
  • メジャーリリース前:手動バックアップを個別保持

この構成では、削除処理が意図どおり動いているかを確認する必要がある。ファイル名の時刻形式が異なる、タイムゾーンが混在する、手動バックアップに削除対象の接頭辞が付くといった問題で、必要なファイルが消えることがある。

削除処理は本番環境でいきなり実行しない。検証用バケットで対象一覧を出力し、削除対象が正しいことを確認する。

暗号化とアクセス制御

バックアップには、ユーザー情報、メールアドレス、決済関連のデータ、管理者情報が含まれる可能性がある。R2への転送が暗号化されていても、バックアップファイルの内容が無防備でよいわけではない。

実務では次の層を分ける。

  • 転送時の暗号化
  • R2バケットの非公開設定
  • APIキーの最小権限
  • バックアップファイル自体の暗号化
  • 復元環境のアクセス制限
  • 操作ログと監査記録

バックアップファイルをパスワード付きで圧縮する場合、パスワードを同じ環境変数ファイルへ保存すると、侵害時に同時に漏れる可能性がある。保管場所を分ける。復元担当者が限られる場合は、鍵へのアクセスも限定する。

R2のACL非対応は、単なる設定上の制約ではない。公開範囲をバケットやアプリケーションの認証層で管理する設計に切り替える必要がある。バックアップ用途では、そもそも公開URLを発行しない構成が適している。

バックアップ基盤を段階的に作る

個人開発サービスで、最初から複雑なバックアップ基盤を構築する必要はない。最初に必要なのは、失敗しにくい単純な経路である。

段階1:日次フルバックアップ

Laravelならspatie/laravel-backup、Docker上のPostgreSQLやMySQLならダンプコマンド、WordPressなら対応プラグインを使う。

この段階では、次を実現する。

  • 毎日決まった時刻に実行する
  • R2へ保存する
  • 直近の世代を保持する
  • 失敗時に通知する
  • 手動で復元できる

保存形式や差分方式より、復元手順を確立することを優先する。

段階2:復元テストと監視

次に、バックアップファイルを実際に復元する。復元できない原因は、バックアップ処理だけにあるとは限らない。

  • ダンプ対象の権限が不足している
  • 圧縮ファイルが破損している
  • 暗号化キーを紛失している
  • アプリケーションのバージョンが合わない
  • マイグレーション履歴とデータベースの状態が一致しない
  • WordPressのファイルパスが変わっている

復元テストで検出できる問題は多い。月次で自動化できれば理想だが、初期段階では手動でもよい。復元した事実を記録することが重要である。

段階3:データ量に応じて方式を変更する

データ量が10GBを超える、バックアップ時間が長くなる、復旧時点を細かくしたい。この段階でLitestream、差分バックアップ、データベースのレプリカを検討する。

方式変更には依存関係がある。LitestreamはSQLite向けであり、PostgreSQLやMySQLのバックアップ方式としてそのまま使えるわけではない。データベースエンジンに応じて、ダンプ、物理バックアップ、レプリケーションを選ぶ必要がある。

また、リアルタイムレプリケーションを導入しても、論理障害への対策にはならない。世代バックアップを削除してはいけない。

Cloudflare R2を使うべきケース、避けるべきケース

R2は、個人開発サービスのバックアップ保存先として適している。ただし、すべての用途で最適とは限らない。

向いているのは、次のようなケースである。

  • Laravel、Docker、WordPressからS3互換APIを利用したい
  • 月間のバックアップ容量が10GB前後に収まる
  • 復元時のデータ転送を何度も行う
  • AWS S3のエグレス費用を避けたい
  • バックアップ処理を自分で管理できる
  • 非公開バケットへアーカイブを保存したい

逆に、次のようなケースでは別の選択肢も比較すべきである。

  • 大容量のバックアップを長期保存する
  • オブジェクト単位のACLが必須である
  • 特定のAWSサービスとの細かい統合が必要である
  • バックアップの監視や復元を完全に外部へ任せたい
  • 監査、保持、削除保留などの要件が厳しい
  • データベースの負荷が高く、論理ダンプだけでは時間内に完了しない

R2は安価な保存先である。しかし、バックアップ設計、復元テスト、通知、権限管理まで自動で解決するサービスではない。保存先の料金を下げても、復元できなければ運用上の価値はない。

まとめ:削減できるのは料金であり、設計責任ではない

Cloudflare R2は、個人開発のデータベースバックアップ無料運用を現実的にする選択肢である。10GBの無料ストレージ、100万回のClass A操作、1,000万回のClass B操作、エグレス無料という条件は、小規模サービスと相性がよい。

Laravelでは、S3互換の保存先としてR2を指定できる。spatie/laravel-backupを使えば、データベースとファイルのバックアップを定期実行できる。ただし、R2はオブジェクト単位のACLに対応していない。public可視性を指定せず、非公開保存を前提にする必要がある。

SQLiteでは、Litestreamによる継続的なレプリケーションが使える。R2向けには、Litestream 0.5.8以降で同時実行数2の制御が適用される。DockerのPostgreSQLやMySQLでは、専用コンテナからダンプを作成し、AWS CLIやrcloneでR2へ転送する構成が管理しやすい。WordPressでは、データベースとアップロードファイルの両方を対象にする。

運用上の改善点は次のとおりである。

  • エグレス無料により、復元テストと環境間コピーのコストを抑えられる
  • 日次フルバックアップから始めると、依存関係と復元手順を単純に保てる
  • R2の無料枠を超える場合は、世代数とファイル容量を先に削減する
  • ACLエラーを避けるため、バックアップ用ディスクは非公開にする
  • 自動化の完了条件はアップロード成功ではなく、復元可能性の確認である
  • リアルタイム同期を導入しても、論理障害対策として世代バックアップを残す
  • APIキー、データベース権限、復元環境を分離すると侵害時の影響範囲を抑えられる

R2で削減できるのは、主にストレージとデータ転送の費用である。バックアップの整合性、通知、復元テスト、権限管理は別途設計する必要がある。その前提を崩さなければ、個人開発サービスの運用費を抑えながら、障害時の復旧経路を確保できる。

Related reading: 個人開発のサーバー選定:VPSによる低コスト管理とPaaSの自動化 and Docker導入の分岐点:個人開発でコンテナ化が必要になる基準.

よくある質問

Cloudflare R2の無料枠はどのくらいですか
Standardストレージが月10GB、Class A操作が月100万回、Class B操作が月1,000万回まで無料です。エグレス(データ転送)も無料です。
R2はAWS S3と完全に互換ですか
S3互換APIを提供していますが、オブジェクト単位のACLヘッダーには対応していません。Laravelでvisibilityをpublicにするとアップロード時にエラーが発生するため、privateを前提にする必要があります。
SQLiteのバックアップにLitestreamを使うメリットは何ですか
データベースの変更を継続的にR2へレプリケーションするため、日次バックアップよりも直近の状態へ復旧できます。ただし誤操作も同期されるため、定期的なスナップショットや世代管理との併用が必要です。
WordPressのバックアップ容量が膨らむ原因は何ですか
元画像と複数サイズのサムネイル、差し替え後の古いファイル、キャッシュファイル、プラグインが生成するログや一時ファイルが原因です。wp-content/cacheなど再生成可能なディレクトリは除外対象にすると容量を抑えられます。
R2の無料枠を超えるとどのくらいの料金がかかりますか
Standardストレージは1GBあたり月額0.015ドル、Class A操作は100万回あたり4.50ドル、Class B操作は100万回あたり0.36ドルです。エグレスは引き続き無料です。

参考情報