お金かかるからストレージサービス使いたくない
個人でシステム開発をしている人の中で「S3とかにお金払いたくない(ケチ)」ということ思ったことがある方は私だけではないと思います。
※私がケチすぎるだけ...?
私はPaasサービスとしてRender.comを利用していますが、public/フォルダにアップロードした画像が、デプロイや再起動で消えてしまうということがあるらしく、画像をpublic/uploadsにあげたものの、すべて消えてしまうということがありました。
この記事では、そんな状況の解決策の一つになる「画像データをデータベースに保存する」手法について書いています。
また、実際に使ってみて感じた注意点についても触れていきます。
コトの背景
私の本番環境は上述の通り Render.com です。
Renderは非常に便利なPaaSですが、Webサービスのファイルシステムは「エフェメラル(非永続)」という仕様になっています。
つまり…
再デプロイや再起動のタイミングで、
publicフォルダに保存したファイルが削除される。
という現象が起きます。
実際に公式ドキュメントにもこう書かれています👇
“Render services have an ephemeral filesystem. Files written to the filesystem disappear on redeploy or restart.”
Render Docs
そのため、画像アップロードをpublic/uploadsに保存するだけでは、本番運用では「デプロイしたら消えていた」という悲劇が起きてしまいます。
最初に、public/uploadsに画像を保存する方法を採用していた私は、上記現象によってすべての画像ファイルが消えてしまいました。
手作業で頑張って大量の画像ファイルあげたのに、すべて消えていた時には、さすがにモチベ下がりましたね…。
データベースに画像を保存するという発想
「画像をファイルとして保存できないなら、いっそデータベースに直接入れてしまおう」というのが今回の発想です。
Railsでは、BLOB(バイナリラージオブジェクト)型のカラムを用意すれば、画像データそのものを保存できます。
この方法は昔から存在する手法です。システム開発において、直感的にはあまりよさそうな方法ではないですが「場合によっては理にかなっている」場面もあります。
Render のようにファイルが揮発する環境や、外部ストレージのコストを避けたい場合 かつ 小規模アプリや個人開発では十分実用的です。
実装方法
Railsのマイグレーション例は以下です。
class CreateUploadedImages < ActiveRecord::Migration[8.0]
  def change
    create_table :uploaded_images do |t|
      t.string :filename
      t.string :content_type
      t.binary :image_data, limit: 10.megabytes
      t.integer :byte_size
      t.timestamps
    end
    add_index :uploaded_images, :filename
  end
end
image_data カラムに t.binary を指定し、10MBまでの画像を格納できるようにしています。
content_type や byte_size を一緒に保存しておくことで、表示時にレスポンスヘッダを正しく設定できます。
保存処理は、以下のように File.binread を使って画像を読み込むだけです。
def create
  file = params[:image]
  uploaded = UploadedImage.new(
    filename: file.original_filename,
    content_type: file.content_type,
    byte_size: file.size,
    image_data: file.read
  )
  uploaded.save!
end
表示時は send_data を使ってブラウザに返します。
def show
  image = UploadedImage.find(params[:id])
  send_data image.image_data, type: image.content_type, disposition: "inline"
end
これで、DBに保存された画像をそのままブラウザで表示できます。
メリットとデメリット
メリット
- Render のような揮発ストレージでも問題なし
 - DBのバックアップだけで復元可能
 - Active Record のトランザクションに統合できる
 - S3やCloudflare R2などの外部サービス契約が不要
 
小さなアプリであれば、これだけで十分運用できます。
特に、個人開発や非商用プロジェクトでは「お金をかけずに完結する」という点が魅力です。
デメリット
- データベースが肥大化する
 - バックアップ・リストアが重くなる
 - DBキャッシュやI/Oが圧迫される
 - 配信パフォーマンスが低い(CDNを挟めない)
 
要するに、スケールしないのが最大の弱点です。
100枚・1000枚レベルなら問題ありませんが、数万枚を超えると確実に厳しくなります。
実際に使ってみて感じたこと
実際にこの方法を導入してみると、想像以上に「小規模開発では快適」でした。
Render の再デプロイでも画像が消えないし、外部ストレージの課金も不要。
ちょっとしたCMSや掲示板、ポートフォリオサイトなどにはぴったりです。
ただし、SELECTでbinaryカラムを安易に取得すると、サーバーが落ちることがあるので注意が必要です。
また、そのほかにもDBの肥大化やバックアップ時間の増加など、将来的に運用コストが上がる可能性があります。
個人ブログだからこそできる力技で、若干その場しのぎ感のある解決策ではありましたが、将来S3に移し替えることもできるので、現状はこれで良しとしています。
まとめ:目的とスケール感で使い分けよう
「画像をDBに保存」は一見するとバッドノウハウのように思われがちですが、
Render のような非永続環境+コスト重視の個人開発では、むしろ“最適解”になりえると思っています。
- 少量の画像を安全に扱いたい
 - 外部ストレージを契約したくない
 - デプロイ時にファイルが消える環境を使っている
 
そんな方には、ぜひ検討してみてはいかがでしょうか?