Jets CloudFront Uploads
For Jets and Rails ActiveStorage, Jets can create a CloudFront CDN to serve the files uploaded by Rails ActiveStorage.
How It Works
Here’s an example of how it works.
CloudFront Uploads Distribution -> Lambda Function URL -> Rails -> ActiveStorage S3
The CloudFront distribution will have a target origin of the “Lambda Function URL”, which serves your Rails app.
Interestingly, ActiveStorage can be configured for S3 or any other storage. CloudFront only knows about the Lambda Function URL.
Config
You can enable the creation of the Uploads CloudFront distribution with the following:
config/jets/deploy.rb
Jets.deploy.configure do
config.uploads.cloudfront.enable = true
config.uploads.cloudfront.cert.arn = acm_cert_arn(domain: "example.com", region: "us-east-1")
# optional since a conventional alias is created
# config.uploads.cloudfront.aliases = [
# "uploads-#{Jets.env}.example.com"
# ]
config.uploads.cloudfront.route53.enable = true
end
The CloudFront aliases config is optional since Jets create a convention CloudFront alias. It looks something like this:
demo-dev-uploads.example.com
If dns.enable = true
, then a route53 record is also created that matches the domain of the cloudfront.cert.arn
.
CDN For Rails ActiveStorage Uploads
Important: For the CloudFront CDN to work, you must use the ActiveStorage in rails_storage_proxy
mode. Here’s one way to configure it.
config/initializers/active_storage.rb
Rails.application.config.active_storage.resolve_model_to_route = :rails_storage_proxy
Jets will also automatically configure a JETS_UPLOAD_HOST
environment variable with the CloudFront endpoint. It will use the first value of config.uploads.cloudfront.aliases
or the conventionally created alias.
So you can create a cdn_image
route helper that will route to the CDN. Example:
config/route.rb
Rails.application.routes.draw do
direct :cdn_image do |model, options|
expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }
route_for_options = {
host: ENV["JETS_UPLOAD_HOST"], # automatically set when creating Jets managed uploads cloudfront distribution
port: ENV["JETS_UPLOAD_PORT"] # only for development
}
if model.respond_to?(:signed_id)
route_for(
:rails_service_blob_proxy,
model.signed_id(expires_in: expires_in),
model.filename,
options.merge(route_for_options)
)
else
signed_blob_id = model.blob.signed_id(expires_in: expires_in)
variation_key = model.variation.key
filename = model.blob.filename
route_for(
:rails_blob_representation_proxy,
signed_blob_id,
variation_key,
filename,
options.merge(route_for_options)
)
end
end
end
Here’s an example of how you would use the helper in the views.
app/views/photos/show.html
<%= image_tag(@photo.image.variant(resize_to_limit: [300, 300])) %>
Rails Docs:
- Rails Guide: Putting a CDN in Front of Active Storage
- Active Storage CDN with Cloudfront and Subdomain in Rails: This blog post does an excellent job of explaining the difference between Rails ActiveStorage redirect vs proxy mode. You must use proxy mode to use a CDN in front of ActiveStorage uploads.
Tips
In general, CDNs like CloudFront cache content. This means errors may be cached if you have an error while setting your configuration. This can be confusing when debugging. Here are a few tips:
- Try adjusting the
cloudfront.default_cache_behavior.cache_policy_id = cloudfront_cache_policy_id("Managed-CachingDisabled")
, for debugging. Docs: Using the managed cache policies - Browsers will cache images also. They simply follow the instructions for the cache headers. You can do a hard browser refresh request with
Shift-Command-R
. You can also use a private or incognito browser while testing. - You can invalidate all CloudFront caches with:
aws cloudfront create-invalidation --distribution-id $DIST --paths '/*'
Related
- CloudFront Assets Distribution: Jets can create a CloudFront distribution and automatically configure it for assets that get precompiled to the
public/assets
folder byrails assets:precompile
. - CloudFront Lambda Distribution: Jets can create a CloudFront distribution and automatically configure it in front of the deployed controller Lambda Function.
- CloudFront Uploads Distribution: Jets can create a CloudFront distribution and automatically configure it for files like your ActiveStorage uploads.
Cache Control Header
With ActiveStorage, you can configure cache-control response header with:
config/storage.yml
amazon:
service: S3
upload:
cache_control: "private, max-age=<%= 1.day.to_i %>"
Note this is meta-data added to the s3 object. So if you want to change the cache-control, you must reupload the object.
For more info see ActiveStorage Docs
Reference
The table below covers each setting. Each option is configured with config.OPTION
. The config.
portion is not shown for conciseness. IE: logger.level
vs config.logger.level
.
Name | Default | Description |
---|---|---|
assets.cloudfront.cert.arn | nil | ACM Cert ARN. Required when using assets.cloudfront.enable = true . Must be in us-east-1 since it’s for CloudFront. This helper method is useful: acm_cert_arn(domain: "example.com", region: "us-east-1") |
assets.cloudfront.cert.minimum_protocol_version | TLSv1.2_2021 | The TLSv1.2_2021 has been the Cloudfront console default as of 12/24/23. |
assets.cloudfront.cert.ssl_support_method | sni-only | The distribution accepts HTTPS connections from only viewers that support server name indication (SNI). This is recommended. Most browsers and clients support SNI. |
assets.cloudfront.default_cache_behavior.allow_methods | %w[HEAD GET] | Allow methods for the distribution. |
assets.cloudfront.default_cache_behavior.properties | {} | Default cache behavior properties to merge. Allows overriding the propertes in a general way. |
assets.cloudfront.default_cache_behavior.viewer_protocol_policy | redirect-to-https | How CloudFront should handle http requests. The default is to redirect http to https. IE: A https upgrade. |
assets.cloudfront.route53.comment | “Jets managed CloudFront distribution DNS record” | Route53 Record comment. |
assets.cloudfront.route53.enable | false | Enables creation of the Route53 DNS Records that match the CloudFront aliases. |
assets.cloudfront.route53.hosted_zone_id | nil | Route53 Hosted Zone ID. This takes higher precedence over hosted_zone_name. |
assets.cloudfront.route53.hosted_zone_name | nil | Route53 Hosted Zone ID. Allows you to specify the config in a human-readable way. Note route53.domain also works as a convenience. |
assets.cloudfront.route53.properties | {} | Route53 DNS record properties to merge. Allows overriding the propertes in a general way. |
assets.cloudfront.route53.ttl | 60 | Route53 DNS TTL. This is only used when assets.cloudfront.route53.use_alias = false and a CNAME is created instead. |
assets.cloudfront.route53.use_alias | true | Use an A Record with the “Alias” Route53 feature. This allows APEX domains to work with CloudFront distributions. |
assets.cloudfront.enable | false | Enables CloudFront Distribution in front of the Lambda URL. See: Lambda URL CloudFront Distribution |
assets.cloudfront.http_version | http2 | HTTP version that you want viewers to use to communicate with CloudFront. |
assets.cloudfront.ipv6_enabled | true | Enables IPV6 also for CloudFront. |
assets.cloudfront.origin.custom_origin_config | { HTTPSPort: 443, OriginProtocolPolicy: “https-only” } | Custom origin config. |
assets.cloudfront.origin.properties | {} | Origin properties to merge. Allows overriding the propertes in a general way. |
assets.cloudfront.origin.viewer_protocol_policy | redirect-to-https | How CloudFront should handle http requests. The default is to redirect http to https. IE: A https upgrade. |
assets.cloudfront.price_class | PriceClass_100 | Price class you want to pay for CloudFront. There’s PriceClass_100, PriceClass_200, PriceClass_All. Note, since the lower price classes use less regions, they deploy faster. |
assets.cloudfront.properties | {} | Properties to merge and override CloudFront Distribution |
assets.cloudfront.spread_hosts | true | Whether or not to create multiple aliases with pattern assets%d.example.com to spread the requests. Related: Rails Assets |
assets.enable | true | Enables Lambda Function URL for the Controller Lambda Function. |