Search code examples
ruby-on-railscarrierwavedigital-ocean-spaces

Rails 6 image upload to digitalocean spaces


I have a blog that uploads banner and thumbnail images. I originally had it set to upload to the local directory using the carrierwave gem and I wanted to try to use digitalocean spaces since the app is deployed to the digitalocean app platform. I am able to get it working on localhost:3000 but when I deploy it to digitalocean it keeps reverting back to a previous deployment saying that a health check failed but not real errors.

I decided to deploy it to heroku since I am able to get actual errors. Here is the current error I get from heroku:

2021-03-09T22:41:10.466080+00:00 heroku[web.1]: Starting process with command `bin/rails server -p ${PORT:-5000} -e production`
2021-03-09T22:41:20.121636+00:00 app[web.1]: => Booting Puma
2021-03-09T22:41:20.121679+00:00 app[web.1]: => Rails 6.1.3 application starting in production
2021-03-09T22:41:20.121679+00:00 app[web.1]: => Run `bin/rails server --help` for more startup options
2021-03-09T22:41:31.105804+00:00 app[web.1]: [fog][WARNING] Unable to fetch credentials: Connection refused - connect(2) for 169.254.169.254:80 (Errno::ECONNREFUSED)
2021-03-09T22:41:31.108732+00:00 app[web.1]: Exiting
2021-03-09T22:41:31.110380+00:00 app[web.1]: /app/vendor/bundle/ruby/3.0.0/gems/fog-core-2.2.3/lib/fog/core/service.rb:244:in `validate_options': Missing required arguments: aws_access_key_id, aws_secret_access_key (ArgumentError)
2021-03-09T22:41:31.110386+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/fog-core-2.2.3/lib/fog/core/service.rb:268:in `handle_settings'
2021-03-09T22:41:31.110391+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/fog-core-2.2.3/lib/fog/core/service.rb:98:in `new'
2021-03-09T22:41:31.110426+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/fog-core-2.2.3/lib/fog/core/services_mixin.rb:30:in `new'
2021-03-09T22:41:31.110427+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/carrierwave-2.2.0/lib/carrierwave/storage/fog.rb:68:in `eager_load'
2021-03-09T22:41:31.110431+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/carrierwave-2.2.0/lib/carrierwave.rb:77:in `block in <class:Railtie>'
2021-03-09T22:41:31.110435+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.3/lib/active_support/lazy_load_hooks.rb:68:in `block in execute_hook'
2021-03-09T22:41:31.110467+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.3/lib/active_support/lazy_load_hooks.rb:61:in `with_execution_control'
2021-03-09T22:41:31.110472+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.3/lib/active_support/lazy_load_hooks.rb:66:in `execute_hook'
2021-03-09T22:41:31.110473+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.3/lib/active_support/lazy_load_hooks.rb:52:in `block in run_load_hooks'
2021-03-09T22:41:31.110508+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.3/lib/active_support/lazy_load_hooks.rb:51:in `each'
2021-03-09T22:41:31.111096+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.3/lib/active_support/lazy_load_hooks.rb:51:in `run_load_hooks'
2021-03-09T22:41:31.111100+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/railties-6.1.3/lib/rails/application/finisher.rb:129:in `block in <module:Finisher>'
2021-03-09T22:41:31.111101+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/railties-6.1.3/lib/rails/initializable.rb:32:in `instance_exec'
2021-03-09T22:41:31.111127+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/railties-6.1.3/lib/rails/initializable.rb:32:in `run'
2021-03-09T22:41:31.111132+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/railties-6.1.3/lib/rails/initializable.rb:61:in `block in run_initializers'
2021-03-09T22:41:31.111132+00:00 app[web.1]: from /app/vendor/ruby-3.0.0/lib/ruby/3.0.0/tsort.rb:228:in `block in tsort_each'
2021-03-09T22:41:31.111164+00:00 app[web.1]: from /app/vendor/ruby-3.0.0/lib/ruby/3.0.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
2021-03-09T22:41:31.111168+00:00 app[web.1]: from /app/vendor/ruby-3.0.0/lib/ruby/3.0.0/tsort.rb:431:in `each_strongly_connected_component_from'
2021-03-09T22:41:31.111169+00:00 app[web.1]: from /app/vendor/ruby-3.0.0/lib/ruby/3.0.0/tsort.rb:349:in `block in each_strongly_connected_component'
2021-03-09T22:41:31.111201+00:00 app[web.1]: from /app/vendor/ruby-3.0.0/lib/ruby/3.0.0/tsort.rb:347:in `each'
2021-03-09T22:41:31.111202+00:00 app[web.1]: from /app/vendor/ruby-3.0.0/lib/ruby/3.0.0/tsort.rb:347:in `call'
2021-03-09T22:41:31.111772+00:00 app[web.1]: from /app/vendor/ruby-3.0.0/lib/ruby/3.0.0/tsort.rb:347:in `each_strongly_connected_component'
2021-03-09T22:41:31.111777+00:00 app[web.1]: from /app/vendor/ruby-3.0.0/lib/ruby/3.0.0/tsort.rb:226:in `tsort_each'
2021-03-09T22:41:31.111777+00:00 app[web.1]: from /app/vendor/ruby-3.0.0/lib/ruby/3.0.0/tsort.rb:205:in `tsort_each'
2021-03-09T22:41:31.111781+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/railties-6.1.3/lib/rails/initializable.rb:60:in `run_initializers'
2021-03-09T22:41:31.111808+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/railties-6.1.3/lib/rails/application.rb:384:in `initialize!'
2021-03-09T22:41:31.111809+00:00 app[web.1]: from /app/config/environment.rb:5:in `<main>'
2021-03-09T22:41:31.111813+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/bootsnap-1.7.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
2021-03-09T22:41:31.111839+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/bootsnap-1.7.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
2021-03-09T22:41:31.111844+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/bootsnap-1.7.2/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
2021-03-09T22:41:31.111844+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/bootsnap-1.7.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
2021-03-09T22:41:31.111845+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/bootsnap-1.7.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
2021-03-09T22:41:31.111845+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/zeitwerk-2.4.2/lib/zeitwerk/kernel.rb:34:in `require'
2021-03-09T22:41:31.111848+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.3/lib/active_support/dependencies.rb:332:in `block in require'
2021-03-09T22:41:31.111851+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.3/lib/active_support/dependencies.rb:299:in `load_dependency'
2021-03-09T22:41:31.111884+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.3/lib/active_support/dependencies.rb:332:in `require'
2021-03-09T22:41:31.112296+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/bootsnap-1.7.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:53:in `require_relative'
2021-03-09T22:41:31.112301+00:00 app[web.1]: from config.ru:3:in `block in <main>'
2021-03-09T22:41:31.112302+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/builder.rb:116:in `eval'
2021-03-09T22:41:31.112334+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/builder.rb:116:in `new_from_string'
2021-03-09T22:41:31.112339+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/builder.rb:105:in `load_file'
2021-03-09T22:41:31.112340+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/builder.rb:66:in `parse_file'
2021-03-09T22:41:31.112367+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/server.rb:349:in `build_app_and_options_from_config'
2021-03-09T22:41:31.112368+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/server.rb:249:in `app'
2021-03-09T22:41:31.112372+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/server.rb:422:in `wrapped_app'
2021-03-09T22:41:31.112398+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/server.rb:312:in `block in start'
2021-03-09T22:41:31.112403+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/server.rb:379:in `handle_profiling'
2021-03-09T22:41:31.112809+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/server.rb:311:in `start'
2021-03-09T22:41:31.112813+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/railties-6.1.3/lib/rails/commands/server/server_command.rb:39:in `start'
2021-03-09T22:41:31.112841+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/railties-6.1.3/lib/rails/commands/server/server_command.rb:144:in `block in perform'
2021-03-09T22:41:31.112845+00:00 app[web.1]: from <internal:kernel>:90:in `tap'
2021-03-09T22:41:31.112846+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/railties-6.1.3/lib/rails/commands/server/server_command.rb:135:in `perform'
2021-03-09T22:41:31.112877+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/thor-1.1.0/lib/thor/command.rb:27:in `run'
2021-03-09T22:41:31.112881+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/thor-1.1.0/lib/thor/invocation.rb:127:in `invoke_command'
2021-03-09T22:41:31.112882+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/thor-1.1.0/lib/thor.rb:392:in `dispatch'
2021-03-09T22:41:31.112912+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/railties-6.1.3/lib/rails/command/base.rb:69:in `perform'
2021-03-09T22:41:31.114191+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/railties-6.1.3/lib/rails/command.rb:50:in `invoke'
2021-03-09T22:41:31.114195+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/railties-6.1.3/lib/rails/commands.rb:18:in `<main>'
2021-03-09T22:41:31.114196+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/bootsnap-1.7.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
2021-03-09T22:41:31.114229+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/bootsnap-1.7.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
2021-03-09T22:41:31.114234+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/bootsnap-1.7.2/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
2021-03-09T22:41:31.114234+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/bootsnap-1.7.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
2021-03-09T22:41:31.114261+00:00 app[web.1]: from /app/vendor/bundle/ruby/3.0.0/gems/bootsnap-1.7.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
2021-03-09T22:41:31.114262+00:00 app[web.1]: from bin/rails:5:in `<main>'
2021-03-09T22:41:31.238223+00:00 heroku[web.1]: Process exited with status 1
2021-03-09T22:41:31.311694+00:00 heroku[web.1]: State changed from starting to crashed

After the server starts it crashes saying fog cannot get the credentials.

Here is my initializer/carrierwave.rb file:

CarrierWave.configure do |config|
  config.fog_credentials = {
    provider:              'AWS',                        # required
    aws_access_key_id:     Rails.application.credentials.aws(:digitalocean_spaces_key),                        # required unless using use_iam_profile
    aws_secret_access_key: Rails.application.credentials.aws(:digitalocean_spaces_secret),                        # required unless using use_iam_profile
    use_iam_profile:       true,                         # optional, defaults to false
    region:                'nyc3',                  # optional, defaults to 'us-east-1'
    host:                  'nyc3.digitaloceanspaces.com',             # optional, defaults to nil
    endpoint:              'https://nyc3.digitaloceanspaces.com' # optional, defaults to nil
  }


  config.fog_directory  = 'judo'                                      # required
  config.fog_public     = true                                                 # optional, defaults to true
  #config.fog_attributes = { cache_control: "public, max-age=#{365.days.to_i}" } # optional, defaults to {}
  # For an application which utilizes multiple servers but does not need caches persisted across requests,
  # uncomment the line :file instead of the default :storage.  Otherwise, it will use AWS as the temp cache store.
  # config.cache_storage = :file
end

Here is another attempt at the carrierwave.rb file:

CarrierWave.configure do |config|
  config.fog_credentials = {
    provider:              'AWS',                        # required
    digitalocean_spaces_key_id:     Rails.application.credentials.digitalocean_spaces_key,                        # required unless using use_iam_profile
    digitalocean_spaces_secret: Rails.application.credentials.digitalocean_spaces_secret,
    digitalocean_spaces_bucket: Rails.application.credentials.digitalocean_spaces_bucket,
    use_iam_profile:       true,                         # optional, defaults to false
    region:                'nyc3',                  # optional, defaults to 'us-east-1'
    host:                  'nyc3.digitaloceanspaces.com',             # optional, defaults to nil
    endpoint:              'https://nyc3.digitaloceanspaces.com' # optional, defaults to nil
  }


  config.fog_directory  = 'judo'                                      # required
  config.fog_public     = true                                                 # optional, defaults to true
  #config.fog_attributes = { cache_control: "public, max-age=#{365.days.to_i}" } # optional, defaults to {}
  # For an application which utilizes multiple servers but does not need caches persisted across requests,
  # uncomment the line :file instead of the default :storage.  Otherwise, it will use AWS as the temp cache store.
  # config.cache_storage = :file
end

Here is my credentials.yml file:

production:
  digitalocean_spaces_key: <%= ENV["DIGITALOCEAN_SPACES_KEY"] %>
  digitalocean_spaces_secret: <%= ENV["DIGITALOCEAN_SPACES_SECRET"] %>
  digitalocean_spaces_bucket: <%= ENV["DIGITALOCEAN_SPACES_BUCKET"] %>

development:
  digitalocean_spaces_key: my_key
  digitalocean_spaces_secret: my_secret
  digitalocean_spaces_bucket: judo

I entered the spaces key, secret, and bucket as app level variables in the app on digitalocean.

Here is my uploaders/banner_uploader.rb file:

class BannerUploader < CarrierWave::Uploader::Base
  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  #include CarrierWave::MiniMagick

  # Choose what kind of storage to use for this uploader:
  # storage :file
  storage :fog

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}"
  end

  # Provide a default URL as a default if there hasn't been a file uploaded:
  # def default_url(*args)
  #   # For Rails 3.1+ asset pipeline compatibility:
  #   # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
  #
  #   "/images/fallback/" + [version_name, "default.png"].compact.join('_')
  # end

  # Process files as they are uploaded:
  # process scale: [200, 300]
  #
  # def scale(width, height)
  #   # do something
  # end

  # Create different versions of your uploaded files:
  # version :thumb do
  #   process resize_to_fit: [50, 50]
  # end

  # Add an allowlist of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  def extension_allowlist
    %w(jpg jpeg gif png)
  end

  # Override the filename of the uploaded files:
  # Avoid using model.id or version_name here, see uploader/store.rb for details.
  # def filename
  #   "something.jpg" if original_filename
  # end
end

Here is my post.rb:

class Post < ApplicationRecord
  belongs_to :user
  has_many :comments, as: :commentable, dependent: :destroy
  # Active Storage from Rails 5
  #has_one_attached :thumbnail
  #has_one_attached :banner
  # Action Text from Rails 6
  has_rich_text :body
  
  mount_uploader :banner, BannerUploader
 
  validates :title, length: { minimum: 5 }
  validates :body,  length: { minimum: 25 }

  self.per_page = 10
  extend FriendlyId
  friendly_id :title, use: :slugged

end

And here is my Gemfile:

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '3.0.0'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main'
gem 'rails', '~> 6.1.3'

# Use Puma as the app server
gem 'puma', '~> 5.0'
# Use SCSS for stylesheets
gem 'sass-rails', '>= 6'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 5.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Active Storage variant
gem 'image_processing', '~> 1.2'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.4', require: false

group :development, :test do
  # Use sqlite3 as the database for Active Record
  gem 'sqlite3', '~> 1.4'
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 4.1.0'
  # Display performance information such as SQL time and flame graphs for each request in your browser.
  # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md
  # gem 'rack-mini-profiler', '~> 2.0'
  gem 'listen', '~> 3.3'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
end

group :production do 
  gem 'pg'
end 

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 3.26'
  gem 'selenium-webdriver'
  # Easy installation and use of web drivers to run system tests with browsers
  gem 'webdrivers'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

gem 'devise'
gem 'simple_form'
gem 'faker'
gem 'will_paginate'
gem 'friendly_id'
gem "carrierwave"
gem "mime-types"
gem "fog-aws"

Solution

  • It seems like the server can't access to the content of the credentials.yml. This file is encrypted and Rails use the master key store in the master.key file to read it. So given that the master.key is not checked into version control, you need to provide it manually on the server. Please check this article https://medium.com/cedarcode/rails-5-2-credentials-9b3324851336

    This section :

    Deploying master key

    When you move your code to a server, you need to make sure that your config/credentials.yml.enc file can be decrypted. That means that somehow you’ll need to provide Rails with your master key, given that it is not checked into version control.

    There are two ways of doing that:

    Option 1: Place the config/master.key file in the server. You’ll normally want to symlink this file to a shared folder in the server filesystem. Again, do not version your config/master.key file.

    Option 2: create a RAILS_MASTER_KEY ENV variable. Rails will detect it and use it as your master key, e.g. in heroku: heroku config:set RAILS_MASTER_KEY=.

    You should be able to use any of those indistinctly.

    If you are using RBENV as you ruby manager, you can store your env var doing this :

    You will create .rbenv-vars in your project folder, not your rails project folder but the folder in which is your project folder, the parent folder if you want.

    After that you put your env var inside the file like this:

    DATABASE_PWD=*****

    no quotes, no space too before ou after the "="

    For more check this link on goRails : https://gorails.com/deploy/ubuntu/18.04#capistrano

    Hope this can help