<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Katherine (she/her)</title>
    <description>The latest articles on Forem by Katherine (she/her) (@katherine).</description>
    <link>https://forem.com/katherine</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F555523%2F9a3c18cc-7dca-46c6-a2b3-8b768c6bcb41.png</url>
      <title>Forem: Katherine (she/her)</title>
      <link>https://forem.com/katherine</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/katherine"/>
    <language>en</language>
    <item>
      <title>AWS Systems Manager (SSM) Cross Region Replication</title>
      <dc:creator>Katherine (she/her)</dc:creator>
      <pubDate>Wed, 12 Jan 2022 20:07:20 +0000</pubDate>
      <link>https://forem.com/customink/aws-systems-manager-ssm-cross-region-replication-3ah3</link>
      <guid>https://forem.com/customink/aws-systems-manager-ssm-cross-region-replication-3ah3</guid>
      <description>&lt;h2&gt;
  
  
  Overview of SSM Replication
&lt;/h2&gt;

&lt;p&gt;This blog post will explain in detail how to set up cross region replication for &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html"&gt;AWS Parameter Store&lt;/a&gt;. As of the writing of this blog post, AWS does not have a native feature for replicating parameters in SSM. If you are using SSM Parameter Store instead of Secrets Manager and are seeking a way to replicate parameters for DR/Multi-Region purposes, this post may help you.&lt;/p&gt;

&lt;p&gt;Diagram showing the architecture setup:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--G7w26Z4I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kwcx4ega1k3xtmyw2127.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--G7w26Z4I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kwcx4ega1k3xtmyw2127.png" alt="Architecture setup for ssm replication" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless Framework Setup
&lt;/h2&gt;

&lt;p&gt;I used &lt;a href="https://github.com/customink/lamby-cookiecutter"&gt;Lamby&lt;/a&gt; cookie-cutter as the framework for this Lambda, which made a lot of the initial set up very easy! Please take a look at that site &amp;amp; set up your serverless framework for the work to be done ahead. I will first share the CloudFormation template used, then share the code that makes the replication work as well as plain in detail what's happening.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AWS SSM regional replication for multi-region setup
Parameters:
  StageEnv:
    Type: String
    Default: dev
    AllowedValues:
      - test
      - dev
      - staging
      - prod
Mappings:
  KmsMap:
    us-east-1:
      dev: 'arn:aws:kms:us-east-1:123456:key/super-cool-key1'
      staging: 'arn:aws:kms:us-east-1:123456:key/super-cool-key2' 
      prod: arn:aws:kms:us-east-1:123456:key/super-cool-key3'
    us-east-2:
      dev: 'arn:aws:kms:us-east-2:123456:key/super-cool-key1'
      staging: 'arn:aws:kms:us-east-2:123456:key/super-cool-key1'
      prod: 'arn:aws:kms:us-east-2:123456:key/super-cool-key1' 
  DestinationMap:
    us-east-1: 
      target: "us-east-2"
Resources:
  ReplicationQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub 'SSM-SQS-replication-${StageEnv}-${AWS::Region}'
      VisibilityTimeout: 1000
  LambdaRegionalReplication:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: .
      Handler: lib/ssm_regional_replication.handler
      Runtime: ruby2.7
      Timeout: 900
      MemorySize: 512
      Environment:
        Variables:
          STAGE_ENV: !Ref StageEnv
          TARGET_REGION: !FindInMap [DestinationMap, !Ref AWS::Region, target]
          SKIP_SYNC: 'skip_sync'
      Events:
        InvokeFromSQS:
          Type: SQS
          Properties:
            Queue: {"Fn::GetAtt" : [ "ReplicationQueue", "Arn" ]}
            BatchSize: 1
            Enabled: true
        ReactToSSM:
          Type: EventBridgeRule
          Properties:
            Pattern:
              detail-type:
                - Parameter Store Change 
              source:
                - aws.ssm
      Policies:
      - Statement:
        - Sid: ReadSSM
          Effect: Allow
          Action:
          - ssm:GetParameter
          - ssm:GetParameters
          - ssm:PutParameter
          - ssm:DeleteParameter
          - ssm:AddTagsToResource
          - ssm:ListTagsForResource
          Resource: 
          - !Sub "arn:aws:ssm:*:${AWS::AccountId}:parameter/*"
      - Statement:
        - Sid: DecryptSSM
          Effect: Allow
          Action:
          - kms:Decrypt
          - kms:Encrypt
          Resource: 
          - !FindInMap [KmsMap, us-east-1, !Ref StageEnv]
          - !FindInMap [KmsMap, us-east-2, !Ref StageEnv]
  LambdaFullReplication:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: .
      Handler: lib/ssm_full_replication.handler
      Runtime: ruby2.7
      Timeout: 900
      MemorySize: 512
      Environment:
        Variables:
          STAGE_ENV: !Ref StageEnv
          TARGET_REGION: !FindInMap [DestinationMap, !Ref AWS::Region, target]
          SKIP_SYNC: 'skip_sync'
      Events:
        DailyReplication:
          Type: Schedule
          Properties:
            Description: Cronjob to run replication at 9:30am EST every Wednesday (cron is UTC)
            Enabled: True 
            Name: DailySSMReplication
            Schedule: "cron(30 13 ? * 4 *)"
      Policies:
      - Statement:
        - Sid: SQSPerms
          Effect: Allow
          Action:
          - sqs:SendMessage
          Resource: 
          - !Sub "arn:aws:sqs:*:${AWS::AccountId}:SSM-SQS-replication-*"
      - Statement:
        - Sid: ReadSSM
          Effect: Allow
          Action:
          - ssm:GetParameter
          - ssm:GetParameters
          - ssm:PutParameter
          - ssm:AddTagsToResource
          - ssm:ListTagsForResource
          - ssm:DescribeParameters
          Resource: 
          - !Sub "arn:aws:ssm:*:${AWS::AccountId}:*"
          - !Sub "arn:aws:ssm:*:${AWS::AccountId}:parameter/*"
      - Statement:
        - Sid: DecryptSSM
          Effect: Allow
          Action:
          - kms:Decrypt
          - kms:Encrypt
          Resource: 
          - !FindInMap [KmsMap, us-east-1, !Ref StageEnv]
          - !FindInMap [KmsMap, us-east-2, !Ref StageEnv]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above template does a number of things. It creates my SQS queue, a regional replication lambda that is event based, and a full replication lambda that is cron based. Under the 'Mappings' section I have "KmsMap" which maps to the aws/ssm KMS keys. If you use other keys for your SSM entries, enter that value here. If you use &lt;em&gt;many&lt;/em&gt; keys across your SSM parameters, simply add them to the lambda properties, example here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      - Statement:
        - Sid: DecryptSSM
          Effect: Allow
          Action:
          - kms:Decrypt
          - kms:Encrypt
          Resource: 
          - !FindInMap [KmsMap, us-east-1, !Ref StageEnv]
          - !FindInMap [KmsMap, us-east-2, !Ref StageEnv]
          - 'arn:aws:kms:us-east-1:123456:key/my-managed-key1' 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The other 'Mapping', &lt;code&gt;DestinationMap&lt;/code&gt;, sets up my source and target region. My original SSM parameters are in &lt;code&gt;us-east-1&lt;/code&gt;, so the target is &lt;code&gt;us-east-2&lt;/code&gt; in this case. The SQS queue holds all of the parameters from the &lt;code&gt;LambdaFullReplication&lt;/code&gt;, since lambdas cannot run indefinitely, there's a high chance the function won't finish before going through all of your parameters. This &lt;code&gt;LambdaFullReplication&lt;/code&gt; function sends the parameters to the SQS queue, where the &lt;code&gt;LambdaRegionalReplication&lt;/code&gt; then performs the put action to the destination region. The &lt;code&gt;VisibilityTimeout&lt;/code&gt; is set to &lt;code&gt;1000&lt;/code&gt; to allow some wiggle room for the lambda (&lt;code&gt;900&lt;/code&gt;). &lt;br&gt;
The full replication lambda runs every Wednesday (or whatever frequency you'd like) for a few reasons: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;to do the initial get/put for the parameters and&lt;/li&gt;
&lt;li&gt;to catch any parameters that have/delete the skip_sync tag &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I will discuss the &lt;code&gt;skip_sync&lt;/code&gt; tag in detail when discussing the code. The regional replication lambda runs when there's an entry in the SQS queue that has to be processed, or anytime there's a change to a parameter, driven by event based actions.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code Setup
&lt;/h2&gt;

&lt;p&gt;Next I will discuss and share the Ruby code that actually does the work. There are three Ruby files that make this lambda function, &lt;code&gt;parameter_store.rb&lt;/code&gt;, &lt;code&gt;ssm_regional_replication.rb&lt;/code&gt;, and &lt;code&gt;ssm_full_replication.rb&lt;/code&gt;. I will share the code along with the comments around what is happening in the file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require 'aws-sdk-ssm'
# Create ParameterStore class, to be shared by both regional
# and full replication lambda.
class ParameterStore
  # The parameter store class creates instance variables with "attr_accessor" 
  # for the initial client, response, name, and tag_list. 
  attr_accessor :client, :response, :name, :tag_list
  # Initialize method for hash
  # this allows the client &amp;amp; name instance vars
  # to be used outside of the init method
  def initialize(h)
    self.client = h[:client] # this gets the client key from CloudWatch metrics
    self.name = h[:name] # gets the name of the param &amp;amp; assigns it to name instance var
  end
  # this method takes the client &amp;amp; name args from prev method.
  def self.find_by_name(client, name) 
    # create new client connection &amp;amp; name from private `find_by_name` method
    new(client: client, name: name).find_by_name(name)
  end
  private 
  def find_by_name(name)
    # set begin block in order for the get_parameter call to
    # loop through all of the parameters
    begin
      # declare instance variable with self.response
      # set to the AWS client connection calling
      # get_parameter method via Ruby CLI
      # extract the name &amp;amp; with_decruption options set
      self.response = client.get_parameter({
        name: name,
        with_decryption: true,
      })
      # rescue to look for AWS SSM throttling errors.
      # take the exception below, and place in variable "e"
    rescue Aws::SSM::Errors::ThrottlingException =&amp;gt; e 
      p "Sleeping for 60 seconds while getting parameters."
      sleep(60)
      # will re-run what is in begin block
      retry
    end
    self
  end

  # creates a `tag_list` instance var
  # `||=` operator is Ruby "short-circuit" which means
  # if `tag_list` is set, then skip this part,
  # if not set, then set it to what is on the right side of equals sign.
  # the purpose is to set the tag_list var equal to
  # the response from the `list_tags_for_resource`¹ 
  # which contains resource_type set to Parameter, and the 
  # resource_id set to name
  def tag_list
    @tag_list ||= client.list_tags_for_resource({resource_type: 'Parameter', resource_id: name})
  end
  # checks the `tag_list` method above &amp;amp; runs a 
  # select method on the tag_list hash
  # loops to see if there is a key with the `key` value in hash
  # and checks presence of a `skip_sync` tag with the `.any?` 
  # boolean method. If this exists, then the lambda function
  # will not run and the replication will not occur.
  # If this does not exist, then it proceeds. 
  # You may want to skip syncing for regional specific resources. 
  # If you want to replicate an initial skip_sync param, simply
  # remove the tag in question and on the next run, the param will sync`
  def skip_sync?
    tag_list[:tag_list].select {|key| key[:key] == $skip_tag }.any?
  end
  # Calls the Ruby `put_parameters` method on the `client_target` parameter.
  # `put_parameter` replicates name, value, type, and overwrite. This method
  # also adds the tags copied over from the tag_list method to resources by name.
  def sync_to_region(client_target)
    client_target.put_parameter({
      name: response['parameter']['name'], # required
      value: response['parameter']['value'], # required
      type: response['parameter']['type'], # accepts String, StringList, SecureString
      overwrite: true,
    })
    client_target.add_tags_to_resource({resource_type: 'Parameter', resource_id: name, tags: tag_list.to_h[:tag_list]})    
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;The next file I will discuss is the &lt;code&gt;ssm_full_replication.rb&lt;/code&gt; piece of the code. As you may gather from the name, this is responsible for full replication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# this pulls the AWS sdk gem
require 'aws-sdk-ssm'
require 'aws-sdk-sqs'
require_relative 'parameter_store'
# Declare global variables which are set to the
# respective values from CloudFormation template.
$target_region = ENV['TARGET_REGION'] or raise "Missing TARGET_REGION variable."
$skip_tag = ENV['SKIP_SYNC'] or raise "Missing skip_sync tag."
$stage_env = ENV['STAGE_ENV']
# method set to us-east-1 for source region. 
# var `sqs_client` set to new SQS client connection in target region
# var `sts_client` set to new STS client conn in source region.
# call `send_message` on `sqs_client` var with queue_url &amp;amp; message_body as params.
def send_params_to_sqs(name)
  region = "us-east-1"
  sqs_client = Aws::SQS::Client.new(region: $target_region)
  sts_client = Aws::STS::Client.new(region: region)
  sqs_client.send_message(
    queue_url: "https://sqs.#{region}.amazonaws.com/#{sts_client.get_caller_identity.account}/SSM-SQS-replication-#{$stage_env}-#{region}",
    message_body: name
  )
end
# sets new SSM client connection in source region
# and new SSM client_target connection in target region
def handler(event:, context:)
  client = Aws::SSM::Client.new
  client_target = Aws::SSM::Client.new(region: $target_region)
  # next_token set to nil, which is important at start of lambda func
  next_token = nil
  # loop starts with begin block which
  # runs before the rest of the code in method.
  loop do 
    begin
      # describe_batch is set to value from 
      # describe_parameters² call on the client variable.
      @describe_batch = client.describe_parameters({
        # parameter_filter limits request results to what we need
        parameter_filters: [
          {
            key: "Type",
            values: ["String", "StringList", "SecureString"]
          },
        ],
        # next_token is set to next set of items to return
        next_token: next_token,
      })
      # describe_batch var calls iterative loop and
      # sends param name to send_params_to_sqs method
      @describe_batch.parameters.each do |item|
        send_params_to_sqs(item.name)
      end
      # break means that func will end if the next_token value is empty.
      break if @describe_batch.next_token.nil?
      next_token = @describe_batch.next_token
      # exception handling. it looks for this error message, and this is how it will handle, by pausing for 60 seconds.
    rescue Aws::SSM::Errors::ThrottlingException
      p "Sleeping for 60 seconds while describing parameters."
      sleep(60)
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;The last file to share is the &lt;code&gt;ssm_regional_replication.rb&lt;/code&gt; file. This file is event based and does the regional replication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# this pulls the AWS sdk gem
require 'aws-sdk-ssm'
require_relative 'parameter_store'
# Global vars for file
$target_region = ENV['TARGET_REGION'] or raise "Missing TARGET_REGION variable."
$skip_tag = ENV['SKIP_SYNC'] or raise "Missing skip_sync tag."
# CloudWatch sends events in a specific format compared to SQS triggered lambdas
# so this method grabs the values from CloudWatch handles both formats.
def massage_event_data(event)
  # pull out values from a cloudwatch invocation
  operation = event.fetch('detail', {})['operation']
  name      = event.fetch('detail', {})['name']
  return operation,name if operation &amp;amp;&amp;amp; name
  operation = 'Update' 
  name      = event.fetch('Records', []).first['body']
  return operation,name 
end
def handler(event:, context:)
  # set vars called operation and name. output from prev. method.
  # create new client &amp;amp; target vars for SSM
  operation,name = massage_event_data(event)
  client = Aws::SSM::Client.new
  client_target = Aws::SSM::Client.new(region: $target_region)
  # this logic runs event based code. If the operation from 
  # the CloudWatch metrics is equal to either update or create
  # the ps var uses the ParameterStore find_by_name class method
  # and passes the client * name.
  if operation == 'Update' || operation == 'Create'
    ps = ParameterStore.find_by_name(client, name)
    # if the ps var has a skip_sync tag, then the CloudWatch logs
    # you will get what's in the puts string. if there is no tag
    # it syncs to target region.
    if ps.skip_sync?
      puts "This function has been opted out, not replicating parameter."
    else
      ps.sync_to_region(client_target)
    end
  # if the operation is delete in the source region, then the delete_parameter method is called on the
  # client_target and it's also deleted from the target_region to ensure parity.
  elsif operation == 'Delete'
    response = client_target.delete_parameter({
      name: name, # required. go into event, reference the detail key, and the value name
    })
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;References to AWS API docs page:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/SSM/Client.html#list_tags_for_resource-instance_method"&gt;list_tags_for_resource-instance_method&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/SSM/Client.html#describe_parameters-instance_method"&gt;describe_parameters&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to be sure that there are no missed variables, you can always set up a CloudWatch alarm on if your lambda has any failed invocations or if your SQS queue isn't sending any messages. I hope that this has helped others who are looking for a way to replicate SSM parameters in AWS from one region to another. That's the end of the code, I know it is a lot to digest, so if you have any questions please leave a comment and I'll do my best to follow up.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ssm</category>
      <category>lambda</category>
      <category>replication</category>
    </item>
    <item>
      <title>Using Jekyll &amp; AWS Amplify for your blog!</title>
      <dc:creator>Katherine (she/her)</dc:creator>
      <pubDate>Fri, 08 Jan 2021 23:35:01 +0000</pubDate>
      <link>https://forem.com/katherine/using-jekyll-aws-amplify-for-your-blog-5ea9</link>
      <guid>https://forem.com/katherine/using-jekyll-aws-amplify-for-your-blog-5ea9</guid>
      <description>&lt;p&gt;This blogpost will share how I deployed my Jekyll site to AWS, using AWS Amplify and Route53, be warned, it's a bit on the lengthy side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hi, Let's Set Up a Blog!
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jekyllrb.com/" rel="noopener noreferrer"&gt;Jekyll&lt;/a&gt; is a static site generator that makes having a personal website, blog, portfolio, etc., very easy to set up. Being an Operations Engineer, my exposure to HTML &amp;amp; CSS really only took off back in the MySpace days, and it's been a while since I've used either. If you're looking for a low/no code solution, this may be a perfect fit for you.&lt;/p&gt;

&lt;p&gt;What is &lt;a href="https://aws.amazon.com/amplify/" rel="noopener noreferrer"&gt;AWS Amplify&lt;/a&gt;? On AWS's site they describe Amplify as a service that can help to quickly configure backends, deploy frontend static websites, and more, while supporting many frameworks.&lt;/p&gt;

&lt;h4&gt;
  
  
  Requirements Check
&lt;/h4&gt;

&lt;p&gt;Jekyll does have a fairly robust &lt;a href="https://jekyllrb.com/docs/" rel="noopener noreferrer"&gt;docs&lt;/a&gt; page and I was able to confirm my local machine met the requirements (currently I am on MacOS 11.1, Ruby 3.0, and RubyGems 3.2.3). I actually found it easier to follow the actual blog set up process by following the README's on individual themes instead of following the default on the docs page (&lt;code&gt;jekyll new myblog&lt;/code&gt;). Granted, not all themes are up to date and have active contributions, but the theme that I landed on did.&lt;/p&gt;

&lt;p&gt;*** IMPORTANT FOR RUBY 3 ***&lt;/p&gt;

&lt;p&gt;There's a known bug as of the writing of this blog post where Jekyll fails to serve on Ruby 3. Check out this &lt;a href="https://github.com/jekyll/jekyll/issues/8523" rel="noopener noreferrer"&gt;open issue&lt;/a&gt;. TL;DR: I ran this: &lt;code&gt;bundle add webrick&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;I decided on the &lt;a href="https://github.com/mmistakes/minimal-mistakes" rel="noopener noreferrer"&gt;Minimal Mistakes&lt;/a&gt; theme and went to work using the &lt;a href="https://mmistakes.github.io/minimal-mistakes/docs/quick-start-guide/" rel="noopener noreferrer"&gt;quick start guide&lt;/a&gt;. First, I added this theme to my Gemfile, did a bundle install, and then added my theme to _config.yml - this was a breeze. Once that was done, I tried to confirm the base image was ok by running &lt;code&gt;bundle exec jekyll serve&lt;/code&gt; in my terminal.&lt;/p&gt;

&lt;p&gt;After I confirmed the out of the box config works, I pushed an initial commit to &lt;a href="https://docs.github.com/en/free-pro-team@latest/github/importing-your-projects-to-github/adding-an-existing-project-to-github-using-the-command-line" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. I &lt;em&gt;highly&lt;/em&gt; recommend using some sort of version control and committing after a big change that doesn't break your functionality. You don't want to to be put in a situation where you don't know what your last known working config was without having a good version control system.&lt;/p&gt;

&lt;h4&gt;
  
  
  Updating _config.yml
&lt;/h4&gt;

&lt;p&gt;In my _config.yml I enabled features that I wanted, such as; Google Analytics (pro tip: set your provider to: &lt;code&gt;google-gtag&lt;/code&gt;, didn't work for me if other providers were listed), comments, adding a photo, my domain, etc. I ran my local Jekyll server again to confirm it looked good and it did, so I moved on to creating the appropriate root files for links/nav/pages.&lt;/p&gt;

&lt;h4&gt;
  
  
  Links, Assets, Navigation, &amp;amp; Content
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Added a "resume.markdown" file which points to a folder in my "assets/images/FILE_NAME" path to show an image of my resume. &lt;/li&gt;
&lt;li&gt;Created "tag-archive.md" to set up the tags feature for better filtering of future blog posts. &lt;/li&gt;
&lt;li&gt;Created "navigation.yml" &amp;amp; "ui-text.yml" in a root level directory I made called "_data" to cover the navigation for my site.&lt;/li&gt;
&lt;li&gt;Updated the "about.markdown" file to just have a basic about me post.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Hosting on AWS
&lt;/h3&gt;

&lt;p&gt;Ok! Once our blog works on our local machine &amp;amp; looks good, we can move on to deploying it somewhere so the rest of the world can see 😄 I decided to go with AWS Amplify for now, but may go with GitHub pages since Jekyll is hosted for free on there.&lt;/p&gt;

&lt;p&gt;First thing I did was make sure my domain was still valid, if you do not own a domain, you can purchase one! Simply google "buy a domain" and you'll get a ton of results, do your research and make sure you find the one that best fits your needs. &lt;/p&gt;

&lt;p&gt;Once I confirmed my domain was good, I created a Terraform file for my Route53 entry. If you are not familiar with Terraform, you can easily do this in the AWS Console instead.&lt;/p&gt;




&lt;h5&gt;
  
  
  SIDE NOTE
&lt;/h5&gt;

&lt;p&gt;you must have an AWS account to proceed with the tutorial after this point, some features are free and fall into the free tier, some are not. PLEASE be aware of the resources you are spinning up and using, it can be a very costly cloud provider.&lt;/p&gt;




&lt;p&gt;To create an entry in Route53 with &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone#public-zone" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt;, all you need is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_route53_zone" "primary" {
  name = "example.com"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;very easy! It creates the nameservers and other resources after doing a &lt;code&gt;terraform apply&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Alternatively, if you do not want to use Terraform, in the AWS console, you can go to the Route53 page (search for this under all services) and then select the "Hosted Zones" on the left hand side. Then click "Create Hosted Zone" and enter the information requested and click the "Create Hosted Zone" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F387gqemwzeu6oxr3uzew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F387gqemwzeu6oxr3uzew.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a few minutes, DNS should have propagated out, take note of your "NS" (nameserver) entries, we will need these values later on for our custom domain.&lt;/p&gt;

&lt;h4&gt;
  
  
  AWS Amplify
&lt;/h4&gt;

&lt;p&gt;In the AWS Amplify console, select "New app" &amp;amp; "Host web app" from the drop down. The next page will have you select a source code option, choose the provider that you have (I use GitHub).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7psyrd6a3raxvic0lxdi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7psyrd6a3raxvic0lxdi.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next page will redirect you to GitHub for authorization to your repo. Once that's done select your blog repo from the dropdown menu &amp;amp; branch if it's something other than main that you would like to deploy. &lt;/p&gt;

&lt;p&gt;The next step and the final one before the review is to confirm the build settings. PRO TIP: If you are planning on using Google Analytics, edit the build in Amplify to include the &lt;code&gt;JEKYLL_ENV=production&lt;/code&gt; before  if not, it will default to the development environment, which won't push your data to Google.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyl24ohkaea1nf0a4ev6h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyl24ohkaea1nf0a4ev6h.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To add your custom domain to AWS Route53, go to the "DNS Settings" page of your hosting provider (Google Domains, Go Daddy, etc..) and select the "Custom" option shown by your provider. Enter the nameservers exactly as shown from the AWS Route53 entry for your hosted zone and hit save.&lt;/p&gt;

&lt;p&gt;Now, go back to the "Domain management" tab on your app in the AWS Amplify console, and select your domain that was registered in Route53 and select "Configure Domain" to the right of your domain. Make sure this option is selected: &lt;code&gt;Setup redirect from https:// to https://www.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzwtxz3re84i626pk057r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzwtxz3re84i626pk057r.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it! Now we have a Jekyll blog hosted on AWS Amplify using Route53. Quite a process, but it's great to expose yourself to Cloud native solutions and explore more about what AWS has to offer.&lt;/p&gt;

&lt;p&gt;If you have any questions, comments, or feedback, please leave a comment.&lt;/p&gt;

&lt;p&gt;Thank you for reading! :)&lt;/p&gt;

</description>
      <category>jekyll</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
