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
Lamby 🆕 HTTP API Support     GitHub