Developer Experience

Amplify Console - The dynamic solution to static hosting on AWS

Never manage your own CloudFront / S3 buckets again!
Michael Barney Trek10
Michael Barney | Feb 12 2020

A Familiar Story

You need to host a static site on AWS, so following CloudFormation-first development, you write a CloudFormation template that defines these resources:

  • S3 bucket - to store the site
  • CloudFront distribution - to serve content from the S3 bucket
  • CloudFront origin access identity - to give the distribution an entity to trust
  • S3 bucket policy - to trust the CloudFront origin access identity
  • A couple of Route53 record sets - to set up DNS to the distribution

Developing this template takes time, and every time you iterate on the CloudFront distribution, you have to wait 20 to 30 minutes for the distrubution to finish updating. This hurdle can be one of the biggest hits to your productivity.

Even when you have the template completed (or if you pulled a template from an example online), you still need to set up CI on your frontend code to upload the built site to the created S3 bucket. After that, you need to create a CloudFront cache invalidation to make sure your users are getting the right assets. You might even need to build some custom Lambda@Edge functions to handle redirects or rewrites, depending on how your website is structured. That will add another painful developer experience since you must wait 20-30 minutes every time that function is updated so the CloudFront distribution can finish updating.

There has to be another way, right? Right.

Amplify Console - The Right Step Forward

Not to be confused with other services under the AWS "Amplify" umbrella, Amplify Console provides a way to host your website with full CI/CD, almost entirely (and serverlessly) managed for you. So let's get a site up and running.

We'll follow CloudFormation-first development and start with a template. Amplify Console's most basic resource type is the AWS::Amplify::App resource. The only required property here is Name. While you should let CloudFormation name your resources in most cases, Amplify App names are not required to be unique. The most basic amplify app would look like this:

Resources:
  AmplifyApp:
    Type: AWS::Amplify::App
    Properties:
      Name: my-website

This CloudFormation will create an app that doesn't offer much in terms of CI; you'll have to upload your site whenever you want to deploy something manually. So let's fix that:

Parameters:
  Repository:
    Type: String
    Description: GitHub Repository URL
  OauthToken:
    Type: String
    Description: GitHub Repository URL
    NoEcho: true
  Branch:
    Type: String
    Description: The name of the branch to deploy off of
Resources:
  AmplifyApp:
    Type: AWS::Amplify::App
    Properties:
      Name: my-website
      Repository: !Ref Repository
      OauthToken: !Ref OauthToken
      AccessToken: !Ref OauthToken
  AmplifyBranch:
    Type: AWS::Amplify::Branch
    Properties:
      BranchName: !Ref Branch
      AppId: !GetAtt AmplifyApp.AppId
      EnableAutoBuild: true

Here we've added a Repository and an OauthToken property to the amplify app. The token will be used to create a webhook and read-only deploy key for your third-party provider. For Github, the token will be a "Personal Access Token." For Bitbucket, unfortunately, CloudFormation is not yet well supported. As for the repository, this is expected to be the https url to the repository, without the trailing .git.

We've also added an AWS::Amplify::Branch resource. Amplify Console treats a git "branch" as a deployment "environment." For most connected apps, a branch is an environment, but for apps that are not connected, this terminology can be confusing; just remember branch == environment.

Now, whenever we push a commit to our repository, Amplify Console will pick that up and spin off a deployment.

What about a custom domain?

Parameters:
  Repository:
    Type: String
    Description: GitHub Repository URL
  OauthToken:
    Type: String
    Description: GitHub Repository URL
    NoEcho: true
  Branch:
    Type: String
    Description: The name of the branch to deploy off of
  Domain:
    Type: String
    Description: Domain name to host application
Resources:
  AmplifyApp:
    Type: AWS::Amplify::App
    Properties:
      Name: my-website
      Repository: !Ref Repository
      OauthToken: !Ref OauthToken
      AccessToken: !Ref OauthToken
  AmplifyBranch:
    Type: AWS::Amplify::Branch
    Properties:
      BranchName: !Ref Branch
      AppId: !GetAtt AmplifyApp.AppId
      EnableAutoBuild: true
  AmplifyDomain:
    Type: AWS::Amplify::Domain
    Properties:
      DomainName: !Ref Domain
      AppId: !GetAtt AmplifyApp.AppId
      SubDomainSettings:
        - BranchName: !GetAtt AmplifyBranch.BranchName
          Prefix: ''
        - BranchName: !GetAtt AmplifyBranch.BranchName
          Prefix: www

We added a Domain parameter that feeds right into an AWS::Amplify::Domain resource. This resource enables you to connect a custom domain to an amplify app. If you are using Route53 for DNS, and the hosted zone for the domain you want is in the same account, a record set will automatically be added for you. You can even configure prefixes, like the common www prefix, as shown in the template.

This resource also provisions an ACM SSL certificate for you. It again will use DNS-verification with a Route53 hosted zone of the same domain automatically, as long as it's in the same account. Note that it may take a few minutes for the cert verification to complete.

And that's it; now you're making use of a fully-managed frontend CI/CD platform without needing to configure your own CloudFront distributions, ACM certificates, or Route53 records. That's not all, there's plenty of features Amplify Console has to offer.

How do we use Amplify Console at Trek10?

In a recent project that was under a multi-account strategy, we had a dev, staging, and production account. We set up a process to deploy the amplify CloudFormation template in each account. The production account's app targeted the master branch, the staging account's app targeted a test branch, and the dev account's app targeted a develop branch. We also configured feature branch deployments on the dev account's app, so we could have frontend devs work on features in parallel, without stepping on any other frontend dev's work. Since Amplify Console provides you with continuous deployment, this is our frontend CI/CD platform.

Custom Build Steps

Amplify Console will automatically detect what framework you're using, whether it's React, Angular, Vue, or even site generators like VuePress, Jekyll, and Gatsby. However, if you have other build requirements, you can place a buildspec-like file in the root of your git repository called amplify.yml. This file follows a buildspec-like format, and is documented here. The Amplify App can also contain the build steps if that's more desirable. Just include the buildspec under Properties. You can also include an IAMServiceRole property to give your build IAM permissions:

  AmplifyRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: amplify.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: Amplify
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: amplify:*
                Resource: '*'
  AmplifyApp:
    Type: AWS::Amplify::App
    Properties:
      Name: my-website
      Repository: !Ref Repository
      OauthToken: !Ref OauthToken
      AccessToken: !Ref OauthToken
      IAMServiceRole: !GetAtt AmplifyRole.Arn
      BuildSpec: |
        version: 1.0
        frontend:
          phases:
            ...

You can also add environment variables to the build so that you can inject things like an API url environment variable. For a react app, it would look something like this:

  AmplifyApp:
    Type: AWS::Amplify::App
    Properties:
      ...
      EnvironmentVariables:
        - Name: REACT_APP_API_URL
          Value: !Sub https://api.${Domain}

Auto-Branch Creation

With Amplify Console, you can configure your app so that the branch that gets created under a specific wildcard match (like feature/*) will automatically create a new branch/environment and deploy that branch to it. To configure this in CloudFormation:

  AmplifyApp:
    Type: AWS::Amplify::App
    Properties:
      AutoBranchCreationConfig:
        AutoBranchCreationPatterns:
          - feature/*
        BasicAuthConfig:
          EnableBasicAuth: True
          Password: password
          Username: admin
        EnableAutoBranchCreation: True
        EnableAutoBuild: True
        Stage: PULL_REQUEST # PRODUCTION | BETA | DEVELOPMENT | EXPERIMENTAL | PULL_REQUEST

Pull-Request Previews

This feature is available for private GitHub repositories only. You must install the Amplify App first.

With Amplify Console, you can configure your app so that every pull request deploys to a unique environment, with a unique preview url. When the pull request is closed, the unique environment will be deleted with it. To set this up in CloudFormation, add the EnablePullRequestPreview and PullRequestEnvironmentName properties to your AWS::Amplify::Branch resource.

  AmplifyBranch:
    Type: AWS::Amplify::Branch
    Properties:
      EnablePullRequestPreview: True
      PullRequestEnvironmentName: prenv

Alternatively you can set this up in the AutoBranchCreationConfig section of an AWS::Amplify::App resource, which will apply to dynamically-created amplify branches:

  AmplifyApp:
    Type: AWS::Amplify::App
    Properties:
      AutoBranchCreationConfig:
        EnablePullRequestPreview: True
        PullRequestEnvironmentName: prenv

Email Notifications

This feature is not supported in CloudFormation yet, but you can configure email notifications to receive updates on your app's deployment. You'll get emails when builds start, fail, and succeed, on a branch-by-branch basis.

In the Amplify console for your app, select "Email notifications" on the left, and from there, you can manage emails that should receive notifications per branch.

Basic Auth

You can configure basic auth to password-protect your sites. This can be applied to the entire app, or just the auto-created environments (such as pull-request previews or environments created from the AutoBranchCreationConfig).

Access Logs

With Amplify Console you can view access logs to your website. In the console, you can click on the "Access Logs" section and view access logs for each domain source under your app. This includes the date, time, host header, status, and useragent. You can query within a specific time range and even download the logs in csv format. The console also allows you to search through logs to find exactly what you need.

Rewrites and Redirects

You can use redirects to reroute navigation from one url to another, which comes in handy when developing a Single Page App like a react app where you want navigation to be routed to /index.html. The documentation has common rewrite and redirect rules. This can remove the need for most Lambda@Edge functions for far less operational cost.

There are four parts to a redirect:

  • Source - the requested address
  • Target - the address that should serve the content
  • Status - the type of redirect (such as 200 rewrite or permanent redirect 301)
  • Condition - A two-letter country code, which enables you to segment your audience by region

To set this up in CloudFormation:

  AmplifyApp:
    Type: AWS::Amplify::App
    Properties:
      ...
      CustomRules:
        - Source: /documents
          Target: /documents/us/
          Condition: <US>
          Status: '302'

Instant Cache Invalidation

Apps deployed through amplify get the benefit of instant cache invalidation. When a build completes, and your app is deployed, Amplify will instantly invalidate the cache serving the site.

Conclusion

There's a lot more that Amplify Console can do, including running Cypress tests and rendering screenshots of your app on various devices. It offers a ton of value in terms of letting AWS manage the heavy lifting, so you don't have to. If you've got a static site, take advantage of Amplify Console, and never worry about CloudFront, bucket permissions, or cache invalidation again!

Author
Michael Barney Trek10
Michael Barney

Michael started his career out in the serverless world, joining the Trek10 team right out of college - a true Serverless Native.