Setting up a Rails App to run production in Heroku saving images uploaded via Carrierwave into AWS S3 bucket

I have had a mightily frustrating day trying to figure out how to save images uploaded using carrierwave into a remote database hosted by Amazon Web Services.

That comes directly after another very seriously frustrating day where I managed to crash our production site, hosted on Heroku, because (who knew?) it didn’t store image files.

Apparently it is ephemeral.

I was apoplectic.

Photo by FuYong Hua on Unsplash

I signed up for AWS yesterday, handed over my credit card details and then scoured the internet for helpful tutorials, while waiting for my S3 access to be authorised.

The helpful tutorials sent me down one rabbit hole after another. AWS informed me I needed gem x, carrierwave proposed gem y and z, the helpful bloggers of the interweb went for a combination approach of x,y and z but with a troubling inclusion of deprecated gems like figaro.

I’m getting used to wading my way through deep, muddy pools of uncertainty and copy-pasting my way towards a working app but I was actually flummoxed.

It didn’t help that I haven’t fathomed out how to test our production environment anywhere other than in our production environment – which is the public facing app on Heroku. As I’d bolloxed that up the day before, and we don’t seem to have found our public yet, it didn’t seem so bad to run re-build after re-build as I tinkered with the code. But it was a slow, sloooow process and error testing on production must be terribly bad form!

I did eventually get it to work and I learned some some pretty useful debugging tips along the way so I thought I better jot down some steps just in case I ever have to become an AWS code-sleuther in the future.

Initial steps – note these didn’t work for me. They may for you but more importantly they worked enough to give me some very valuable stack traces.

The most detailed post that includes great visual guides to setting up S3 on AWS is Switching CarrierWave to use S3 with Heroku and localhost – Firehose Project. This uses the deprecated Figaro though and also flips all environments to AWS which broke my local development environment as well – see lightbulb moment below though.

I ended up using this guide by JiaHung Lin as it allowed me to split the handling of files between file storage for the local development environment and AWS for production (but again, note my retrospective lightbulb moment documented below).

If using this JiaHung’s guide I would suggest getting carrierwave setup to work locally before muddying the waters with S3. There will be more detailed guides for that stage. If you’ve done that you can just skip to the section called *Upload to s3*

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# config/initializers/carrierwave.rb
CarrierWave.configure do |config|
if Rails.env.staging? || Rails.env.production?
config.fog_provider = 'fog/aws'
config.fog_credentials = {
:provider =>; 'AWS',
:aws_access_key_id =>; ENV['AWS_ACCESS_KEY_ID'],
:aws_secret_access_key =>; ENV['AWS_SECRET_ACCESS_KEY']
}
config.fog_directory = ENV['S3_BUCKET_NAME']
config.storage = :fog
else
config.storage = :file
config.enable_processing = Rails.env.development?
end
end

The Heroku logs revealed two issues or CLUES.

  1. Fog is telling me that I’m not using the right region and so it’s going to redirect. It suggests I uses US-West-2
  2. The redirect fails but looking at the hostname it clearly looks odd as my S3 bucket name is duplicated.


Google tells me that I can amend the region to remove the Fog error message.
Fog AWS repeated bucket name issues:

So I change the carrierwave.rb file to include reference to the region rather than relying on the default:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# config/initializers/carrierwave.rb
CarrierWave.configure do |config|
if Rails.env.staging? || Rails.env.production?
config.fog_provider = 'fog/aws'
config.fog_credentials = {
:provider =>; 'AWS',
:aws_access_key_id =>; ENV['AWS_ACCESS_KEY_ID'],
:aws_secret_access_key =>; ENV['AWS_SECRET_ACCESS_KEY'],
:region =>; 'us-west-2'
}
config.fog_directory = ENV['S3_BUCKET_NAME']
config.storage = :fog
else
config.storage = :file
config.enable_processing = Rails.env.development?
end
end

This still didn’t fix my problem. Clue 1 has disappeared but my hostname still has the double bucket issue.

Google stops being helpful so I nip over AWS which has the world’s worst user interface but actually a very handy developer guide. It revealed something about virtual paths which had a format very similar to my dodgy hostname. It led me to think that perhaps the duplication was happening as a result of this redirection so I needed to find a way to stop that – a get it right first time approach.

The logs helped again. The redirected, double bucket path was actually heading off to a totally different region us-east-2.

So we finally got our production site working when carrierwave.rb looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# config/initializers/carrierwave.rb
CarrierWave.configure do |config|
if Rails.env.staging? || Rails.env.production?
config.fog_provider = 'fog/aws'
config.fog_credentials = {
:provider =>; 'AWS',
:aws_access_key_id =>; ENV['AWS_ACCESS_KEY_ID'],
:aws_secret_access_key =>; ENV['AWS_SECRET_ACCESS_KEY'],
:region =>; 'us-east-2'
}
config.fog_directory = ENV['S3_BUCKET_NAME']
config.storage = :fog
else
config.storage = :file
config.enable_processing = Rails.env.development?
end
end
Photo by Kendall Ruth on Unsplash

Lightbulb Moment

Having struggled so much with AWS over the last couple of days, I felt compelled to document the route to success but rather annoyingly I had forgotten to screenshot all the error logs – hence why I haven’t illustrated the double bucket name error log!

I’ve now spent a few more hours trying to recreate the same problems in another project and in doing so I have had a bit of a lightbulb moment.

*Now that we’ve fixed the problem, I’m convinced that it probably is a good idea to have development and production use the same storage solution. So what I thought was a flaw in the original guide may well actually answer my question of how the heck do you test the production without breaking production?*

So in my new project I have coded the solution like this:

1
2
3
4
5
6
7
8
9
# config/initializers/carrierwave.rb

CarrierWave.configure do |config|
config.fog_provider = 'fog/aws' config.fog_credentials = {
provider: 'AWS',
aws_access_key_id: ENV["AWS_ACCESS_KEY_ID"], aws_secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"], region: 'eu-west-2'
}
config.fog_directory = ENV["AWS_BUCKET"]
end

and

1
2
3
4
5
# app/uploaders/image_uploaders

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

This has the benefit of using AWS for file upload when run from localhost (development) and also Heroku (production).