Asset Host & Precompiling
For those that are used to precompiling assets on their production servers, Lambda may introduce a new way of thinking. Instead, assets are precompiled during the SAM build phase. We recommend using an :assets
group in your Gemfile
and changing your application.rb
to load this group for development and test environments. ⚠️ Assets require a custom domain name to link properly.
group :assets do
gem 'sass-rails'
gem 'uglifier', require: false
end
Bundler.require(*Rails.groups.tap { |groups|
if Rails.env.development? || Rails.env.test?
groups << 'assets'
end
})
Our Cookiecutter & Serving Static Assets
Our Quick Start cookiecutter project uses the above technique and keeps things simple by leveraging Rails built in ability to serve static assets. We do this by setting this environment variable in app.rb
.
ENV['RAILS_SERVE_STATIC_FILES'] = '1'
We also add this configuration to your config/environments/production.rb
file. In this case we are setting the cache control to 30 days, which you can change. The X-Lamby-Base64
header signals to the Lamby rack adapter that the content requires base64 binary encoding.
config.public_file_server.headers = {
'Cache-Control' => "public, max-age=#{30.days.seconds.to_i}",
'X-Lamby-Base64' => '1'
}
Using S3 & CloudFront As An Asset Host
Your application's assets may be impractical to package and serve from Lambda. Or maybe you want a real CDN like CloudFront to globally serve your assets. For whatever reason, using CloundFront with an S3 bucket as the origin is pretty easy to setup.
Configure Your Rails Asset Host
In your production.rb
, or any other environment file that uses an asset host, configure Rails with the domain you intent to use. We also recommend that your asset manifest be removed from the public dir and placed in your application config instead. This way, it can be packed and deployed along side your application. Open up your config/initializers/assets.rb
file and set the manifest.
config.action_controller.asset_host = 'https://assets.example.com'
Rails.application.config.assets.manifest = Rails.root.join('config/manifest.json')
Sync Assets to S3 During Builds
In order to get the compiled assets to our S3 bucket, we need to hook into the bin/_build script. Add this line after the assets:precompile
line. It uses the AWS CLI s3 sync command to copy new assets to your S3 bucket. It also sets a cache control header for a proper origin response.
aws s3 sync ./public/assets "s3://assets.example.com/assets" \
--cache-control "public, max-age=31536000"
Create a CloudFront Distro Backed by S3 with CloudFormation
To create our CloudFront distribution and S3 bucket, we are going to use CloudFormation to express our Infrastructure as Code (IaC). The work here was inspired DJ Walker's Automate Your Static Hosting Environment With AWS CloudFormation tutorial but re-written from the ground up to be more inline with Rails static asset host best practices. Simply log into the AWS Console, and go to CloudFormation -> Create stack -> Template is ready -> Upload a template file.
Prior to executing this template, please use AWS Certificate Manager to create an SSL cert for you domain name and change the AcmCertificateArn
to the ARN of your certificate. Also make sure to change assets.example.com
to your domain name.
AWSTemplateFormatVersion: '2010-09-09'
Description: MyApp Asset Host
Resources:
AssetHostBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: assets.example.com
AssetHostBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref AssetHostBucket
PolicyDocument:
Statement:
- Action: 's3:GetObject'
Effect: Allow
Resource: !Sub 'arn:aws:s3:::${AssetHostBucket}/*'
Principal:
CanonicalUser: !GetAtt AssetHostDistributionIdentity.S3CanonicalUserId
AssetHostDistributionIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: !Ref AssetHostBucket
AssetHostDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Aliases:
- assets.example.com
Comment: assets.example.com
DefaultCacheBehavior:
AllowedMethods: [ HEAD, GET ]
CachedMethods: [ HEAD, GET ]
Compress: true
DefaultTTL: 31536000
MinTTL: 31536000
ForwardedValues:
Cookies:
Forward: none
QueryString: false
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
Enabled: true
HttpVersion: http2
Origins:
- DomainName: !GetAtt 'AssetHostBucket.DomainName'
Id: S3Origin
S3OriginConfig:
OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/${AssetHostDistributionIdentity}'
ViewerCertificate:
AcmCertificateArn: YOUR_CERT_ARN_HERE
SslSupportMethod: sni-only