I'm running a Rails 4 app on Heroku, using CloudFront as the CDN for some image assets. I have a Production app and a Staging app. On CloudFront, I have separate distributions for Production and Staging. I'm precompiling assets locally and pushing them to Heroku.
My production.rb has the following:
# production.rb
config.action_controller.asset_host = 'xxxx.cloudfront.net'
# Use special staging CDN if pushing to staging
config.action_controller.asset_host = 'yyyy.cloudfront.net' if ENV['PUSH_TO'] && ENV['PUSH_TO'].downcase == 'staging'
So I'm specifying a different asset_host depending on if I'm pushing to staging or production.
However, when I push to staging, the resulting images are missing and pointing to the Production CloudFront distribution.
I put some logging statements in the app to ensure that config.action_controller.asset_host is set to the correct Staging distribution.
If I manually access the image urls by changing the host from the Production host to the Staging host they work, so I know the fingerprints updated correctly.
My .css.scss.erb file contains:
.importance1 { background-image: image-url("<%= asset_path 'myImage1.png' %>"); }
I made sure to clear my public/assets and tmp folders.
So what could be causing my assets on Staging to be pointing to the wrong CloudFront distribution?
Update: I've discovered that only the assets that are referred to with asset_path from other assets (css files) are getting the wrong CDN host. Meanwhile, assets that are displayed from views using image_tag are working fine. This makes me think that the problem is during precompiling assets, the wrong url's are getting output from asset_path in the css files. Not sure why though. I'm calling rake RAILS_ENV=production assets:clean assets:precompile and like I've said, I tried modifying production.rb to explicitly use the staging CDN only as a test and it still didn't use it (the assets still point to the Production CDN.)
I finally figured out my problem.. Even though my staging app was using the correct asset_host, and the files were being served from the correct CDN, the urls within those files were pointing to the wrong CDN (the production CDN.) This was because while precompiling assets, the PUSH_TO env variable wasn't set, so the logic in my production.rb was using the production CDN. The solution was to do:
rake RAILS_ENV=production PUSH_TO=staging assets:clean assets:precompile
The key was the PUSH_TO=staging in there. This fixed all my problems!
It's clear that your conditional on this line is returning false:
config.action_controller.asset_host = 'yyyy.cloudfront.net' if ENV['PUSH_TO'] && ENV['PUSH_TO'].downcase == 'staging'
Either that or the asset_host won't allow being set twice. Don't know why that would be. Might be better to put that in an else block.
So fix that and you'll be set.
BUT....
Isn't this what environment files are for? Why don't you have this in your environments/staging.rb file? If it doesn't work there, you've fugged up your environment configuration and kinda defeated the whole purpose of multiple environments...
Related
Trying out rails 7, with tailwind and postcss.
I have a few stylesheets in assets/stylesheets, which I import through a file called imports.css in the same folder.
//imports.css
#import stylesheet1.css;
#import stylesheet2.css;
I then import that file in application.html.erb with the following:
//application.html.erb
<%= stylesheet_link_tag "application", "imports", "data-turbo-track": "reload" %>
On development everything works fine as intended, meshes well with tailwind. Importing works fine due to postcss. However on production (nginx, puma) it tries to pull in those files and fails. For every stylesheet I have, i see in the browser console:
GET https://mywebsite.com/assets/style/stylesheet1.css net::ERR_ABORTED 404 (Not Found)
I am trying to shift my brain over from webpacker in rails 6, not sure what I need to do here... some thoughts:
Do i need to set it up so that the stylesheets get copied over to the public/assets folder or something? Is there a setting to do that in production.rb? (I feel like rails by default should be already doing this)
Do I need to be manually precompiling these assets on deployment?
Help would be appreciated. Thanks!
#stellenberger was right and deserves the credit. In my case their solution of enabling the public file server for static assets fixed this issue for me and immediately started serving the css assets from public. I was already precompiling, they just weren't being served. I am using Rails docker containers on an EC2 in AWS behind an ALB, and there is no local NGINX so the default assumed is not correct for my scenario and needed to be changed.
The config/environments/production.rb file already provides an environment variable that will turn on the public server if present, called RAILS_SERVE_STATIC_FILES so all you need to do is set that environment variable in your build or deploy process. Since it is a presence check, any value will be truthy as the var existence is all that is checked.
Alternatively, you could also set
# config/environments/production.rb
config.public_file_server.enabled = true
in the file instead of using the ENV var, but generally the ENV vars are better than hard-coded configs.
In my case, I changed the default to
# config/environments/production.rb
config.public_file_server.enabled = ENV.fetch("RAILS_SERVE_STATIC_FILES") { true }
that way my default is true and enabled without setting the var, but I can still use the var to disable it if desired.
Do I need to be manually precompiling these assets on deployment?
Yes. Since you're not using a Node workflow now, instead relying on Sprockets and assets directly, you need the precompile step in Production.
Setting the environment variables RAILS_ENV=production and RAILS_SERVE_STATIC_FILES=true did the job for me (with Rails 7.0.4).
I have to precompile assets locally in order for one of my JS plugins to work correctly.
Whenever I make a change to any asset and precompile, I get a new version in public/assets, and the old one is there, too. When I run locally in production mode, I am served a page with the new assets.
When I deploy to EB, the pages always contain links to the old assets.
Of course, application.html.erb uses the dynamic css link tag: <%= stylesheet_link_tag "application", media: "all" %>
production.rb contains:
config.action_controller.perform_caching = true
config.assets.compile = true
I think this must have something to do with Nginx or some sort of caching in EC2 on the html files because puma runs locally.
I have tried:
Different browser, PC, cleared cache, disable cache. It is not the browser.
Setting send file off in nginx.conf.
Setting cache expiration to -1 for html and confirmed with curl that I receive Cache-Control: no-cache
Renamed entire app/current folder. I still receive a page, but it is missing the CSS. Where are the files that are actually used after a server is started?
rake tmp:clear on the server.
Looking all over the server for any nginx or puma cache. I found nothing.
Researching for hours on end over the past 2 years.
The only thing that ever works is to rake assets:clobber, create a new EB environment, and deploy a few times. Sometimes, even that doesn't work.
Please help!
After additional countless hours of failing to solve this, I noticed a variable in the Elastic Beanstalk configuration settings that I had been changing from the default value. RAILS_SKIP_ASSET_COMPILATION - I was setting it to true since I was trying to manage the asset compilation myself. Flipping this back to false fixed my issue and significantly increased my deployment time. My third-party javascripts and gems all work correctly as well.
I still think this is a workaround because I should be able to precompile manually. However, it is good enough for me at this time.
I had a similar issue with caching assets on staging or production server. When I checked Last-Modified attribute of an asset file, for example with curl -I http://url-to-the-asset-file server returned old (cached) file.
What solve the issue was updating assets version in production.rb file. It will force assets to recompile with new MD5 hash fingerprint.
# config/environments/production.rb
# Version of your assets, change this if you want to expire all your assets.
config.assets.version = '1.1'
Hopefully this will help you or at least give some guidance.
I have some images (svg) in my app/assets/images folder. According to the Rails Guides, all the files in the assets folder should be automatically precompiled.
However, when I reference the the image using image_tag('filename'), it shows me an Sprockets::Rails::Helper::AssetNotPrecompiled error
Asset was not declared to be precompiled in production.
It tells me to declare the file to be precompiled manually, but why should that be necessary? On top of that, why does it concern itself with the production environment when I am doing everything in development?
If you added the image after you've started the server in development, restart the server. Sprockets will then precompile that image and the error will go away.
I'm pretty sure Rails doesn't support .svg yet, hence why it would ignore it.
You'll need to include the file extensions in your config/application.rb file:
#config/application.rb
config.assets.precompile += %w(.svg)
In regards the application concerning itself with the production environment, you have to remember that the precompilation process is meant for production:
The first feature of the pipeline is to concatenate assets, which can reduce the number of requests that a browser makes to render a web page. Web browsers are limited in the number of requests that they can make in parallel, so fewer requests can mean faster loading for your application.
Concantenating assets essentially means to compile your asset files into a single file, which is typically then minified.
--
Although this can be done in real-time, it's mostly the realm of static assets (which have to be precompiled). This means that if you run the rake asstes:precompile task, it will work on the development environment, unless you call RAILS_ENV=production rake assets:precompile (which sets it to the production environment for that request.
why does it concern itself with the production environment when I am doing everything in development
The application is going to run in production, not development.
Ultimately, everything you do in development should make it easier / better to work in production. In the sense of your assets, it means that you can use many of the quirks of Rails' asset pipeline, from sprockets to preprocessors such as SASS & Coffeescript
It's probably because you didn't specify the complete image name. I ran into this problem after updating the gem too. Before I just used image_tag 'some-image', but it seems that you now have to specify what type of image/extension you want to use.
Try this: image_tag 'some-image.svg'. It worked for me.
Cheers.
My rails app is working but its slug size is huge (220mb).
I am storing all assets on cloudfront with config.assets.digest = true and config.action_controller.asset_host = "something.cloudfront.net"
heroku run bash
In the slug, there is 30 times application-onedigest.js in public/assets, and same thing for stylesheets and images, so now the slug size is 220 mb.
Maybe one of my gem is doing middleware things (Rails asset_host, cloudfront and heroku) or maybe heroku is trying to optimize things (https://devcenter.heroku.com/changelog-items/328), but i don't think so. I don't want to exceed the maximum heroku slug size that is 300 mb, and i feel that is not normal.
How to remove old rails assets on heroku ?
EDIT
I made little commits in a css file, push to heroku, and compared slug. In the deploy process, there is those lines:
Running: rake assets:precompile
INFO -- : Writing /tmp/build_ff4eb6d7-303e-444d-9c88-938ab504ea8a/public/assets/application-700c7e1849c55312a94a353e60312500.css
Asset precompilation completed (9.60s)
Cleaning assets
Running: rake assets:clean
INFO -- : Removed application-48375ba5495e14b36afab9d4b9d97033.css
This is what i want. But when i compare the old and the new slug, there is the new application-700....css, and an another new mysterious application-a9b33ae58934ad161038cb3ebcee146c.css
And this one is actually used is the heroku webapp (when i explore css resources from the browser, after clearing cache).
So i guess precompile is done twice somewhere?
Temporary solution : heroku fork the app, i am back to 40 mb but there is still the problem.
EDIT
It seems it works when i precompile locally, but i don't like it as it violates 12factors.
Some things you'll want to consider:
rake assets:clobber
Heroku's file system
CDN
rake assets:clobber is an inbuilt way to remove old asset files from your public/assets directory. By running this command, either locally or on Heroku, you'll be able to actively remove the precompiled asset files on your system, allowing you to repopulate the folder with new files
Secondly, you have to remember Heroku runs an ephemeral file system. This means that each time you push your application to Heroku, it will just overwrite any of the files you had before, meaning (importantly), that it's not going to store your last assets. Each time you push, it will precompile the assets for you - which should ensure minimal space taken up with them.
--
Finally, if you find that your assets are taking up too much space on your application server, you may consider using a content delivery network (CDN) to serve the assets you need. You can use the asset_sync gem to push your assets to your CDN of choice
Most people tend to use a CDN with storage system to serve their assets. We use S3 with Cloudfront currently, but are looking to move to Rackspace's file serve system!
You can use the Heroku repo plugin (https://github.com/heroku/heroku-repo) and the repo:purge command to clean all the build assets out of your slug.
Using asset_sync gem is not ideal as you can run into deploy timeouts pushing all the assets over to S3 plus you end up with assets in two places. Remember S3 is not a content delivery network, you're better off using a CloudFront distribution and have it cache the assets directly from your application. See https://devcenter.heroku.com/articles/using-amazon-cloudfront-cdn for more details.
This solve it for me, in production.rb :
config.assets.compile = false
Heroku is no longer generating useless assets. I don't know how to remove the old assets in the same heroku app, but i used "heroku fork" to create a brand new heroku app with the same config / database and without old assets.
heroku run rake assets:clobber
So I started investigating the asset pipeline in Rails3 and I have a desired use case that is somewhat off the beaten path... so I'm looking for a recipe.
I often run webrick locally for development and then run
Passenger+Apache for deployed instances of the app.
the app is configured with a suburi path, e.g. http:// server/approot/...
to make webrick paths work like deployed instances, I added map '/approot' do run app to config.ru. Now webrick is also at http:// local:3000/approot/...
The Confusion
Given this setup, I tried to use rake assets:precompile and have been having a lot of configuration problems between local, deployed -- missing files, incorrect paths, 404s in firebug, etc. Here's a smattering of solutions I've tried:
config.assets.initialize_on_precompile = false to application.rb to prevent trying to initialize the app for production, (we have several deployed environments and call them different names, ug.) when precompiling the assets.
config.assets.precompile += %w( *.js *.css ) to application.rb to include things like jquery.js and ujs and rails.js that were missing.
config.assets.prefix = "/approot/assets" to correct a problem where the map above (in config.ru) doesn't apply to assets, so assets had to be precompiled to ./public/approot/assets, but I'm not sure if that only works locally, i.e. if I deploy, will my asset paths be http:// server/approot/approot/(js|css|...)?
When precompiling assets, the rake task switches to env production, but then it is unclear from the Rails3 doc whether sprockets continues to compile on the fly locally and use those dynamic assets, or whether it will serve the static precompiled assets instead?
I tried putting config.serve_static_assets = true in environments/development.rb, but I'm not sure how this works with config.assets.compress = false and config.assets.debug = true. Setting the assets.debug to false just seems to hide the GET requests in the webrick log, although I saw a post saying that "solved the problem" [sic].
Requirments for a Recipe
So I'd like a recipe that does the following:
assets are consistently and correctly served from a path http://server/approot/assets/... whether run in passenger or webrick (i.e. deployed or local). If this isn't possible, then I can switch my local dev environment to use Passenger+Apache as well, it's not a big deal, but I just want to know if it's possible.
raw assets exist in /app/assets like normal Rails3, but when I precompile them, they work exactly the same way in deployed envs so that asset file references don't break (i.e. right now, there are a lot of refs looking for /assets/image/... when the path is clearly set up as /approot/assets/image.... (It's unclear from the Rails3 doc whether there are assumptions about deploying to root vs a suburi, e.g. http:// server/assets/... vs. http:// server/approot/assets/...)
sprockets can't be used in deployed environments (our restriction, sorry). So this means the rails3 app has to effectively look like a static asset app. I know this is what precompiled assets are supposed to do, but the pathing issues are preventing me from getting this working as advertised.
TL;DR - I feel like I'm trying a lot of separate things that might work if I only knew the right combination of them.
References
http://guides.rubyonrails.org/asset_pipeline.html#precompiling-assets
http://blog.55minutes.com/2012/02/untangling-the-rails-asset-pipeline-part-2-production/
http://blog.55minutes.com/2012/02/untangling-the-rails-asset-pipeline-part-3-configuration/
https://github.com/rails/rails/pull/3946
Ok, here's a potentially horrible answer, but it seems to work with webrick in two contexts now and it's the day after Halloween (although I haven't tried this approach in deployed slots yet).
Configuration
Unless otherwise mentioned, everything is defaults from a rails new app generation.
config/application.rb
config.assets.initialize_on_precompile = false
environments/production.rb (not really production, only used for rake asset:precompile)
config.assets.css_compressor = :yui
config.assets.js_compressor = :uglifier
environments/stage.rb (this is one of our deployed envs)
config.serve_static_assets = true
config.ru
This is the horrible part. I duplicated the map so that Rack would serve both the suburi and the root. So the controller action that shows the layout can have http:// server/approot/foo/index, while the assets within the layout can be loaded from http:// server/assets/...
map '/approot' do
run AppRoot::Application
end
map '/' do
run AppRoot::Application
end
Running it locally
$ rake assets:precompile
$ rails s
and in firebug I see the separate parts served by sprockets (all 200 OK):
GET /approot/
GET /assets/application.css?body=1
GET /assets/jquery.js?body=1
GET /assets/jquery.ujs.js?body=1
GET /assets/application.js?body=1
Ok, so now test a 'deployed' slot locally and see if the compiled assets work?
$ rails s -e stage
and then I see the correct precompiled assets (all 200 OK):
GET /approot/
GET /assets/application-xxxxxxxxxxxxxxx.css
GET /assets/application-xxxxxxxxxxxxxxx.js
Ok, so this isn't as nice as a real suburi solution and I think I'm going to have problems in deployed slots. Round 2, fight!