Ruby

CarrierWaveとは?Rails実装とActive Storageの使い分け【v3.1.3対応】

CarrierWaveとは?Rails実装とActive Storageの使い分け【v3.1.3対応】

CarrierWaveは、Ruby on Railsでファイルアップロードを扱うためのライブラリです。アップロードの仕様をUploaderクラスに書き、モデルの文字列カラム1本にファイル名を保存します。rubygems.orgの累計ダウンロードは1億3,949万件(2026年8月5日時点)で、日本語の解説記事もそのほとんどが2系を前提にしています。しかし現行の最新は3.1.3であり、2系との間には保存タイミングや用語の破壊的変更が入っています。ここでは3.1.3のソースとCHANGELOGを一次ソースとして、導入から制限設定、S3連携、Active Storageとの選び分けまでを整理します。

まとめ

先に結論だけ挙げます。最新は3.1.3(2026年5月23日公開)で、動作要件はRuby 2.5以上・Active Support 6.0以上です。3.1.3はCVE-2026-44587の修正版にあたり、2系にも同日付で2.2.7が出ています。3.1.2以下・2.2.6以下を使っているなら、機能追加の有無にかかわらず先に上げてください。

S3へ置くなら選択肢は2つで、公式READMEが案内するfog-awsか、aws-sdk-s3を直接使うcarrierwave-awsです。そして新規のRailsアプリで今から選ぶなら、既定はActive Storageです。CarrierWaveを選ぶ理由は「既存の文字列カラムをそのままURLに使う資産がある」「保存パスを完全に自分で決めたい」のどちらかに絞られます。以降で、その判断の根拠になる仕様を順に見ていきます。

CarrierWaveの担当範囲とv3.1.3の動作要件

CarrierWaveの中心はUploaderクラスです。保存先・保存パス・画像処理・受け入れ条件をこのクラス1つに書き、モデル側はmount_uploaderで結び付けます。データベースに入るのはファイル名の文字列だけで、実体はローカルディスクかクラウドストレージに置かれます。この「1カラム=1ファイル名」という素朴なデータモデルが、Active Storageとの最大の違いです。

mount_uploaderが生やすメソッドと保存までの流れ

mount_uploader :avatar, AvatarUploaderと書くと、モデルに以下のメソッドが定義されます。v3.1.3のlib/carrierwave/mount.rbで確認できるものです。

メソッド 役割
avatar / avatar= Uploaderオブジェクトの取得・ファイルの割り当て
avatar? ファイルが存在するか
avatar_url 公開URL(版指定は avatar.url(:thumb))
avatar_cache / avatar_cache= 一時保存の識別子
remote_avatar_url= 外部URLからの取り込み
remove_avatar= / remove_avatar! 削除フラグの設定と即時削除
avatar_identifier DBに入っている文字列そのもの
avatar_integrity_error 受け入れ条件違反の例外

複数ファイルを1カラムで扱う場合は複数形のmount_uploadersを使います。生えるメソッド名はカラム名に連動するので、mount_uploaders :avatars, AvatarUploaderならavatars_urlsやremote_avatars_urls=になります。保存の流れは、割り当て時にcache_dirへ一時保存し、レコード保存時にstore_dirへ移動する2段構えです。3.0からはこの移動がafter_commitではなくafter_saveのタイミングになり、トランザクションがロールバックしたときに後片付けする方式へ変わりました。

gemspecが定める依存とimage_processing 1.x固定の影響

v3.1.3のgemspecが要求する条件は次のとおりです。ここを外すとbundle installの時点で止まります。

項目 要求
Ruby >= 2.5.0
activesupport / activemodel >= 6.0.0
image_processing ~> 1.1
marcel ~> 1.0.0
addressable ~> 2.6
ssrf_filter ~> 1.0

注意すべきはmini_magick・rmagick・fog-awsがいずれも開発用依存でしかない点です。画像をリサイズしたければ自分のGemfileにmini_magickを書く必要があります。

もう1つ、実務で効いてくるのがimage_processing ~> 1.1というピンです。この指定は2.x系を許容しません。image_processingでCVEが振られている脆弱性はCVE-2022-24720の1件だけで、こちらは1.12.2で修正済みのため1.x系にも入っています。ただし2.0.0(2026年5月20日)と2.0.1(2026年5月22日)で追加された、ユーザー入力由来の引数によるシェル実行を防ぐハードニングは別です。1.x系の最新は1.14.0(2025年2月10日)のままでバックポートされていません。CarrierWave 3.1.3を使う限り1.x系に固定されるので、変換オプションにユーザー入力を素通しする設計は避けてください。幅や高さは整数に丸め、フォーマット指定は許可リストで受けるのが安全側です。

Railsへの導入手順とアップローダーの雛形

導入は3ステップです。gemを入れ、アップローダーを生成し、モデルのカラムに結び付けます。

# Gemfile
gem "carrierwave", "~> 3.1"
gem "mini_magick"   # 画像処理を使うなら明示的に追加する
$ bundle install
$ bin/rails generate uploader Avatar
      create  app/uploaders/avatar_uploader.rb
$ bin/rails generate migration AddAvatarToUsers avatar:string
$ bin/rails db:migrate

カラム型はstringです。ここに入るのはsample.pngのようなファイル名だけで、ディレクトリはUploader側のstore_dirが決めます。

生成テンプレートの既定値とstore_dirの決め方

ジェネレータが吐くapp/uploaders/avatar_uploader.rbは、v3.1.3では次の形です(コメントアウト部分を実装した状態にしています)。

class AvatarUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick

  storage :file

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  process resize_to_limit: [1200, 1200]

  version :thumb do
    process resize_to_fill: [200, 200]
  end

  def extension_allowlist
    %w(jpg jpeg gif png webp)
  end

  def content_type_allowlist
    %w(image/jpeg image/gif image/png image/webp)
  end
end

既定のstore_dirはモデル名・カラム名・レコードIDを重ねたパスになります。IDを含むので、レコードごとにディレクトリが分かれて衝突しません。一方でfilenameを上書きする場合、公式テンプレートには「ここでmodel.idやversion_nameを使うな」というコメントが入っています。ファイル名がレコードの状態に依存すると、保存後に参照先を見失うためです。ファイル名を固定したいならUUIDなどを初回に決めて別カラムへ持たせてください。

アップローダーの記述が増えてモデル周りが膨らんできたら、RailsのFat Model対策|1500行model.rbを分割する4手法と判断基準で扱っている責務分割の考え方が、そのままUploaderクラスの整理にも使えます。

フォーム実装とバリデーションエラー後の再選択防止

フォームで見落としやすいのが*_cacheのhidden fieldです。これを置かないと、他の項目のバリデーションエラーでフォームが再描画されたとき、ユーザーは画像を選び直す羽目になります。

<%= form_with model: @user do |f| %>
  <%= f.file_field :avatar %>
  <%= f.hidden_field :avatar_cache %>
  <%= f.submit %>
<% end %>

外部URLからの取り込みはremote_avatar_url=で行えます。この経路にはssrf_filterによる保護が入っており、内部ネットワーク宛のURLを渡す攻撃を防ぎます。検証環境などで意図的に無効化したい場合だけskip_ssrf_protectionを設定してください。既定のまま使うのが原則です。

受け入れ条件の設定:allowlistとdenylistの使い分け

CarrierWaveは何も書かなければ任意のファイルを受け取ります。制限は自分で書くもので、書き漏らしはそのまま脆弱性になります。この点はライブラリ側も自覚しており、生成テンプレートのコメントには「これが無いと、安全な拡張子を付けた有害なファイルをアップロードできてしまう」と明記されています。

拡張子・MIMEタイプ・サイズ・画像寸法の4種類

用意されている制限は4系統です。いずれもUploaderクラスでメソッドを上書きして指定します。

def extension_allowlist
  %w(jpg jpeg gif png webp)
end

def content_type_allowlist
  %w(image/)
end

def size_range
  1..5.megabytes
end

def width_range
  100..4000
end

画像寸法のwidth_range・height_rangeは3.0で追加されたもので、2系にはありません。巨大画像を投げつけて変換処理を詰まらせる攻撃への一次防御になります。ただしこの2つは、MiniMagick・RMagick・Vipsのいずれかをincludeしていないと実行時にRuntimeErrorを投げます。寸法を測るのが画像処理モジュール側の役目だからです。制限だけコピーして持ち込むと、アップロード時に初めて落ちます。

content_type_allowlistには文字列も正規表現も渡せますが、文字列を渡した場合の判定は前方一致です。実装は受け取った値をRegexp.quoteしたうえで\Aを先頭に付けて照合します。上の例がimage/という1要素だけで画像系のMIMEタイプを通せるのはこのためです。逆に言えば、image/jpのような中途半端な文字列を書くとimage/jpegもimage/jp2も通り、意図より広く開いてしまいます。エラーメッセージはextension_allowlist_errorやcontent_type_allowlist_errorといったキーでI18nに定義されているので、日本語化はconfig/localesに同じキーを置けば済みます。

Content-Type検査が3度破られた経緯とdenylistを使わない根拠

3系に影響する公開脆弱性は3件あり、いずれもContent-Type検査のバイパスです。並べると傾向がはっきりします。

CVE 公開 影響する版 修正版
CVE-2023-49090 2023-11-29 3.0.0以上3.0.5未満 / 2.2.5未満 3.0.5 / 2.2.5
CVE-2024-29034 2024-03-25 3.0.0以上3.0.7未満 / 2.2.6未満 3.0.7 / 2.2.6
CVE-2026-44587 2026-05-27 3.0.0.beta以上3.1.3未満 / 2.2.7未満 3.1.3 / 2.2.7

内訳を見ると性質が分かれます。古い2件(CVE-2023-49090・CVE-2024-29034)はいずれもcontent_type_allowlistのバイパスで、許可リストを設定していてもすり抜けられ、XSSにつながる可能性がありました。最新のCVE-2026-44587だけが拒否側で、content_type_denylistに渡した文字列の正規表現メタ文字がエスケープされず、拒否リストを回避できるというものです。つまり許可側にも拒否側にも判定漏れの前例があり、どちらか一方を書けば安心という話ではありません。

そのうえで、denylistは使わないでください。これは筆者の推測ではなく、ライブラリ側が明示している方針です。v3.1.3のlib/carrierwave/uploader/content_type_denylist.rbは、denylistを設定して実行すると「セキュリティ上の理由により非推奨であり、何を受け入れて安全かを明示するcontent_type_allowlistを使うこと」という警告を出します。CHANGELOGでも3.0.0.betaの時点で「明示的なオプトインを優先するため#denylistを非推奨とした」と記録されています。拒否リストは「危険なものを列挙し切れている」という成立しにくい前提に立つため、許可側を狭く固定するほうが破綻が少ないという判断です。

あわせて、1.x系と2.0系にはRMagick経由のコードインジェクション(CVE-2021-21305・high)とSSRF(CVE-2021-21288)も出ています。前者はRMagickを使っている場合のみ影響し、後者への恒久対策として3系ではssrf_filterが実行時依存に組み込まれました。3系はどちらの影響範囲にも入りません。2系は2.2.7が3.1.3と同じ2026年5月23日に出ており、放置されてはいませんが、入るのは修正だけです。脆弱性情報そのものの読み解き方は脆弱性とは?種類・CVE/CVSSの仕組みと発見から修正までの実務を解説で整理しています。

画像処理エンジンの選択とversionによる派生生成

v3.1.3のlib/carrierwave/processing/には、mini_magick・vips・rmagickの3ファイルが置かれています。Uploaderでinclude CarrierWave::MiniMagickのようにどれか1つを取り込むと、convert・resize_to_limit・resize_to_fit・resize_to_fill・resize_and_pad・cropが使えるようになります。cropは3.1.0.betaで追加されたものです。

選ぶ基準はシンプルです。既存資産がなければMiniMagickで始めてください。ImageMagickのコマンドラインを叩く方式で情報量が最も多く、CarrierWaveも3.1.0.rcでMiniMagick 5.0以降との非互換を解消済みです。大量の画像を継続的に変換するならlibvipsを使うVipsが有利ですが、乗り換え時にgravityの扱いが違う点に注意してください。v3.1.3のprocessing/vips.rbではresize_to_fillの第3引数が_gravityという捨て変数になっており、渡した値は使われません。MiniMagickで'North'のような非中央のgravityを指定していた場合、例外も警告も出ないまま中央切り抜きへ変わります。gravityが実際に効くのはresize_and_padのほうで、こちらは既定値の表記自体がmini_magick側は'Center'、vips側は'centre'と異なります。

派生画像はversionブロックで定義し、user.avatar.url(:thumb)で参照します。3.0以降はImageMagickが入っていない環境では例外を上げる仕様に変わりました。2系は黙って処理をスキップしていたため、コンテナ移行のタイミングで初めて発覚することがあります。Dockerfileにimagemagickのインストールが入っているか確認してください。

変換はリクエストの中で同期実行されます。数MBの画像から複数サイズを起こすとレスポンスがそのぶん延びるので、サイズが大きい場合は保存だけ先に済ませて変換をジョブへ逃がす構成にします。実装の型はRails Active Jobとは?非同期ジョブ処理の実装とSolid Queue・リトライ設定とSidekiqとは?Rubyで非同期処理を簡単に実現するバックグラウンドジョブツールにまとめています。

S3連携の実装:fog-awsとcarrierwave-awsの選び分け

クラウドストレージへの保存には2つの経路があります。公式READMEが案内するのはfog経由で、S3の場合はfog-aws(最新3.33.2・2026年4月20日)をGemfileに追加します。

CarrierWave.configure do |config|
  config.fog_credentials = {
    provider:              "AWS",
    aws_access_key_id:     ENV.fetch("AWS_ACCESS_KEY_ID"),
    aws_secret_access_key: ENV.fetch("AWS_SECRET_ACCESS_KEY"),
    region:                ENV.fetch("AWS_REGION")
  }
  config.fog_directory = ENV.fetch("S3_BUCKET_NAME")
  config.fog_public    = false
  config.fog_attributes = { cache_control: "public, max-age=#{365.days.to_i}" }
end

もう1つがcarrierwave-aws(最新1.6.1・2026年1月10日)です。fogではなくaws-sdk-s3を直接使う実装で、依存が軽くなります。設定キーはfog版と対応しており、fog_directoryがaws_bucketに、fog_publicがaws_aclに置き換わります。

CarrierWave.configure do |config|
  config.storage    = :aws
  config.aws_bucket = ENV.fetch("S3_BUCKET_NAME")
  config.aws_acl    = "private"
  config.aws_authenticated_url_expiration = 60 * 60 * 24 * 7
  config.aws_credentials = {
    access_key_id:     ENV.fetch("AWS_ACCESS_KEY_ID"),
    secret_access_key: ENV.fetch("AWS_SECRET_ACCESS_KEY"),
    region:            ENV.fetch("AWS_REGION")
  }
end

判断は依存関係で決めてかまいません。アプリがすでにaws-sdk-s3を積んでいるならcarrierwave-awsで揃えるほうがgemが1系統で済みます。逆に、S3以外のプロバイダも併用する予定があるならfogのほうが素直です。carrierwave-aws 1.6.1が許容するCarrierWaveは>= 2.0, < 4なので、3.1.3との組み合わせは問題ありません。

署名付きURLの有効期限は、上の例の60 * 60 * 24 * 7が上限です。S3の署名付きURL自体の仕様上、7日を超える期限は設定できません。しかもIAMロールから受け取る一時認証情報で署名した場合、その認証情報の有効期限が先に来るので実効期限はさらに短くなります。「期限切れリンクの問い合わせが多いから1か月にしよう」という要望は通らないので、期限切れ時に再発行する導線を用意してください。

認証情報をENV.fetchで読んでいるのは、キーをリポジトリに置かないためです。開発環境での読み込ませ方はdotenvとは?.envで環境変数を管理する仕組みと言語別の使い方を参照してください。また、S3互換のオブジェクトストレージを自前で持つ場合はエンドポイントを差し替えるだけで同じ設定が使えます。選択肢はMinIOとは?S3互換オブジェクトストレージの仕組みとOSS版アーカイブ後の採用判断【2026年8月時点】にまとめています。

2.xから3.xへ上げるときに壊れる4か所

3系への移行は、gemのバージョンを書き換えるだけでは済みません。CHANGELOGで破壊的変更と明示されているものを含め、実際に踏みやすい箇所を挙げます。

1つ目は保存タイミングです。3.0.0.betaで、ファイルの確定保存がafter_commitからafter_saveへ移りました。ロールバック時には保存済みファイルを片付ける処理が入りますが、「コミット後にファイルが存在する」という前提でジョブを起動していたコードは見直しが必要です。

2つ目はconvertの拡張子です。3.0.0.rcで、process convert: :jpgのように形式を変換した場合、出力ファイルの拡張子も変換後のものになりました。2系は元の拡張子を保ったままだったため、データベースに残っている既存レコードのファイル名と、新規保存分の命名規則がずれます。2系の挙動を保ちたい場合は、READMEが案内するとおりprocess convert: :jpgの直後にforce_extension falseを書きます。versionの中で変換している場合は保存済みファイルの拡張子が変わるため、recreate_versions!で作り直さないと参照できなくなる点にも注意してください。

3つ目は用語の移行です。whitelist・blacklistはallowlist・denylistへ完全に置き換わりました。旧名も動きますが非推奨警告が出ます。content_type_whitelistを残したままだと、ログに警告が積み上がるうえに、新しく追加されたallowlist側の設定と二重管理になります。

4つ目はfilenameの書き方とメソッド改称です。3.1.0.betaから、2系スタイルでfilenameをガードしていると警告が出るようになりました。あわせて3.1.0.rcでversion_exists?がversion_active?へ改称されています。

動作要件も変わっており、3.0.0.betaでRuby 2.5未満とRails 5.xのサポートが落ちました。MIMEタイプ判定はmini_mimeからmarcelへ差し替わっています。判定ライブラリが変わったことで、これまで通っていた特殊な拡張子が弾かれる可能性がある点も頭に入れておいてください。

移行しない選択、つまり2.2.7で止める判断も成立します。ただしそれは「セキュリティ修正だけ受け取る延命」であって、Rails 8系のアプリで長く使う前提なら3.1.3へ上げるほうが結果的に安くつきます。

Active Storageとの使い分け:新規開発における選定基準

Rails 5.2以降、ファイルアップロードはActive Storageが標準で用意されています。activestorage 8.1.3.1のruntime依存はactionpack・activejob・activerecord・activesupport・marcelだけで、画像変換をするならimage_processingは自分で追加します。この点はCarrierWaveと違い、Active Storage側は変換ライブラリを固定していません。

構造上の差は保存モデルにあります。CarrierWaveは対象テーブルの文字列カラム1本にファイル名を持ちます。Active Storageはactive_storage_blobs・active_storage_attachments・active_storage_variant_recordsという専用テーブルを持ち、has_one_attachedで関連付けます。前者はSQLで直接ファイル名を引ける代わりに、1レコードに複数ファイルを持たせるとカラム設計が苦しくなります。後者は関連テーブルを引く手間がある代わりに、添付の追加でマイグレーションが不要です。

判断ははっきりさせます。2026年に新規のRailsアプリを作るなら、Active Storageを選んでください。CarrierWaveが優位に立つのは次の条件を満たすときだけです。既存の文字列カラムに入ったファイル名をURLの一部としてすでに外部公開しており、パス構造を変えられない場合。あるいはstore_dirでディレクトリ構成を1文字単位まで自分で決めたい要件がある場合。この2つのどちらにも当てはまらないなら、標準機能から外れるコストのほうが大きくなります。

逆に、採用すべきでない場面も明示しておきます。「日本語の実装記事が多いから」という理由だけで新規採用するのは損な選択です。前述のとおり3系に影響する公開脆弱性はすべてContent-Type検査のバイパスであり、このライブラリは受け入れ条件を利用者が自分で書き切る前提で設計されています。content_type_allowlistを書かないまま本番に出す運用が想定されるチームでは、既定でMIMEタイプを検証するActive Storageのほうが事故が減ります。

なお、両者は共存できます。Active Storageは別テーブルで完結するため、既存のCarrierWaveカラムを残したまま、新しい添付だけActive Storageで実装する移行が可能です。一括置換を待たずに新規機能から切り替えるほうが、現実的な移行経路になります。

よくある質問

CarrierWaveの最新バージョンはどれですか?

2026年8月5日時点の最新は3.1.3で、公開日は2026年5月23日です。2系の最新は同日付の2.2.7になります。手元のアプリが実際に読み込んでいる版はbundle info carrierwaveで確認でき、更新可能かどうかはbundle outdated carrierwaveで分かります。Gemfileに~> 3.0と書いていても、Gemfile.lockが3.0.4のまま固定されている例は珍しくないので、lockファイル側の値を見てください。

Rails 8でCarrierWaveは使えますか?

使えます。3.1.3が要求するのはactivesupport・activemodelの6.0.0以上という下限だけで、上限は設けられていません。Rails 7.2との非互換(ActiveSupport::Deprecationの扱い)は3.1.0.betaで、MiniMagick 5.0以降との非互換は3.1.0.rcで解消済みです。逆にRails 5.x以下は3.0.0.betaでサポートが打ち切られているため、その環境では2系を使うことになります。

mount_uploaderとmount_uploadersの違いは?

単数形のmount_uploaderは1カラムに1ファイル、複数形のmount_uploadersは1カラムで複数ファイルを扱います。カラム型は公式READMEが2通りを案内しており、json型を持つPostgreSQLやMySQLではavatars:json、json型が無いSQLiteではavatars:stringにしてモデル側でシリアライズします。ただしRails 8のActive Recordはserializeの位置引数を廃止しているため、READMEのままではArgumentErrorになります。serialize :avatars, coder: JSONと書いてください。

carrierwave-awsとfog-awsはどちらを選ぶべきですか?

すでにaws-sdk-s3を積んでいるアプリならcarrierwave-awsです。fogを追加で抱え込まずに済みます。あとから乗り換える場合の作業は多くありません。config.storageを:fogから:awsへ変え、fog_directoryをaws_bucket、fog_publicをaws_acl、fog_credentialsをaws_credentialsへ読み替えるだけです。保存済みファイルのパスは変わらないので、移行にデータ移動は伴いません。

アップロードした画像のURLはどう取得しますか?

モデルに生えたavatar_urlを呼ぶか、Uploaderオブジェクト経由でavatar.urlを呼びます。versionで定義した派生画像はavatar.url(:thumb)のように版名を渡して取得します。ファイルが無いときにnilを返させたくない場合は、Uploaderでdefault_urlを上書きして代替画像のパスを返してください。S3でfog_publicやaws_aclを非公開にしている場合、返るのは有効期限付きの署名付きURLです。

関連記事

お気に入りに入れた記事の一覧

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.08 テックブログ 大阪公立大学のランサムウェア被害と仮想化基盤の停止|全授業休講に至った経緯とバックアップを守る設定
  2. 2026.10.06 テックブログ アフラックの情報漏洩440万人|大量照会を止められなかった原因と照会量制御の実装
  3. 2026.10.07 テックブログ 旭化成ファーマのサイバー攻撃:Pharma DIGITAL会員51.4万人の漏えいと委託先DBの監視設計
  4. 2026.10.06 テックブログ 焼肉きんぐの不正アクセスと1,078万件の会員情報|全件規模の流出を防ぐAPIとログの点検
  5. 2024.06.11 コラム 個人情報漏えい件数の推移をグラフで解説|最新データと過去最多(約1.9万件)

RELATED POSTS 関連記事

目次