<?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: Daniel Slapelis</title>
    <description>The latest articles on Forem by Daniel Slapelis (@danielslapelis).</description>
    <link>https://forem.com/danielslapelis</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%2F161634%2Fa95fbe1d-ef5b-4320-b4fa-bb0f9afe00c7.jpg</url>
      <title>Forem: Daniel Slapelis</title>
      <link>https://forem.com/danielslapelis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/danielslapelis"/>
    <language>en</language>
    <item>
      <title>Building a Simple CI/CD Pipeline with GitLab, Terraform, and Amazon Web Services</title>
      <dc:creator>Daniel Slapelis</dc:creator>
      <pubDate>Wed, 12 May 2021 13:17:29 +0000</pubDate>
      <link>https://forem.com/danielslapelis/building-a-simple-ci-cd-pipeline-with-gitlab-terraform-and-amazon-web-services-2108</link>
      <guid>https://forem.com/danielslapelis/building-a-simple-ci-cd-pipeline-with-gitlab-terraform-and-amazon-web-services-2108</guid>
      <description>&lt;h1&gt;
  
  
  How to build a CI/CD pipeline using GitLab for your business's website.
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;a href="//gitlab.com"&gt;GitLab&lt;/a&gt; account&lt;/li&gt;
&lt;li&gt;An &lt;a href="//aws.amazon.com"&gt;AWS&lt;/a&gt; account&lt;/li&gt;
&lt;li&gt;
&lt;a href="//terraform.io"&gt;Terraform&lt;/a&gt; is installed&lt;/li&gt;
&lt;li&gt;A &lt;a href="//keybase.io"&gt;KeyBase&lt;/a&gt; account&lt;/li&gt;
&lt;li&gt;A domain managed in Route53&lt;/li&gt;
&lt;li&gt;An ACM certificate for your domain.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Set up the infrastructure
&lt;/h3&gt;

&lt;p&gt;We'll be using Terraform to build out the infrastructure. For the website, all we'll need is an S3 bucket and a CloudFront deployment.&lt;/p&gt;

&lt;p&gt;Create a file named main.tf and paste this into. You can change the bucket name to whatever you want, just make sure you set this correctly later on in another file (you'll see).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"bucket_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"website.example.com"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"cnames"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"www.example.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"certificate_arn"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:::acm::example23132423"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"bucket"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;acl&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt;
  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Sid": "AddPerm",
          "Effect": "Allow",
          "Principal": "*",
          "Action": "s3:GetObject",
          "Resource": "arn:aws:s3:::${var.bucket_name}/*"
      }
  ]
}
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;  &lt;span class="nx"&gt;website&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;index_document&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"index.html"&lt;/span&gt;
    &lt;span class="nx"&gt;error_document&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"index.html"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;billing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billing_tag&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;s3_origin_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"S3-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudfront_distribution"&lt;/span&gt; &lt;span class="s2"&gt;"s3_distribution"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;domain_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket_regional_domain_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;origin_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3_origin_id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;wait_for_deployment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;is_ipv6_enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;default_root_object&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"index.html"&lt;/span&gt;

  &lt;span class="nx"&gt;aliases&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cnames&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nx"&gt;default_cache_behavior&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;allowed_methods&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"DELETE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"HEAD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"OPTIONS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"PATCH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"PUT"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;cached_methods&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"HEAD"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;target_origin_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3_origin_id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="nx"&gt;forwarded_values&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;query_string&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

      &lt;span class="nx"&gt;cookies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;forward&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"none"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;viewer_protocol_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"redirect-to-https"&lt;/span&gt;
    &lt;span class="nx"&gt;min_ttl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;default_ttl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;
    &lt;span class="nx"&gt;max_ttl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


  &lt;span class="nx"&gt;price_class&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PriceClass_100"&lt;/span&gt;

  &lt;span class="nx"&gt;restrictions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;geo_restriction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;restriction_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"none"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;custom_error_response&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;error_code&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;
    &lt;span class="nx"&gt;error_caching_min_ttl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;response_code&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="nx"&gt;response_page_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/index.html"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;viewer_certificate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;acm_certificate_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;certificate_arn&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;ssl_support_method&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sni-only"&lt;/span&gt;
    &lt;span class="nx"&gt;minimum_protocol_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"TLSv1.2_2018"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create the bucket&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform init
terraform apply -auto-approve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Awesome! All of our infrastructure is set up in AWS and now we need to set up our GitLab runner!&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure our runner
&lt;/h3&gt;

&lt;p&gt;In GitLab, create a repository for your project, or use an existing repository. We just need to do a few things before our app is ready to deploy.&lt;/p&gt;

&lt;p&gt;First, let's set up our runner. We're going to use a shared runner from GitLab. They're free to use up for up to 2,000 minutes of deployments per month -- and they're enabled by default.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8elgjh8tvs3el88oi5g5.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8elgjh8tvs3el88oi5g5.png" alt="shared runners"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need to give it an AWS account to use to deploy to S3. Create another terraform config with this content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"keybase_user"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A keybase username to encrypt the secret key output."&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dannextlinklabs"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_access_key"&lt;/span&gt; &lt;span class="s2"&gt;"gitlab_ci"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;user&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_iam_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gitlab_ci&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;pgp_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"keybase:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keybase_user&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_user_policy"&lt;/span&gt; &lt;span class="s2"&gt;"gitlab_ci"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gitlab-ci-policy"&lt;/span&gt;
  &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_iam_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gitlab_ci&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:GetObject",
                "s3:GetObjectAcl",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::website.example.com/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "cloudfront:*",
            "Resource": "*"
        }
    ]
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_user"&lt;/span&gt; &lt;span class="s2"&gt;"gitlab_ci"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gitlab-ci"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"access_key"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_iam_access_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gitlab_ci&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"secret_access_key"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_iam_access_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gitlab_ci&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encrypted_secret&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure you set the keybase user to your own keybase user. Okay whatever, run the config.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform init
terraform apply -auto-approve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The terraform config returns an access key and a secret key for this user. We need to decrypt the secret key with the command (this is why you needed to use your own keybase user).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform output encrypted_secret | base64 --decode | keybase pgp decrypt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have the access key and the secret key for our GitLab user, we need to supply those variables to our runner by adding them to the variables section in the CI/CD settings.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fi7pzyvy1erk0a2fgv59k.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fi7pzyvy1erk0a2fgv59k.png" alt="variables"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We set three variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS_ACCESS_KEY_ID - to the key we just got
AWS_SECRET_ACCESS_KEY - to the key we just got
AWS_DEFAULT_REGION - us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run a deployment
&lt;/h3&gt;

&lt;p&gt;GitLab CI/CD is based around a file called &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;. Our file needs to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deploy-s3&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deploy-cf&lt;/span&gt;

&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;AWS_BUCKET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;website.example.com&lt;/span&gt;

&lt;span class="na"&gt;deploy_s3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python:3.6&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-s3&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gce&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pip install awscli -q&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;aws s3 sync . s3://$AWS_BUCKET/ --delete --acl public-read&lt;/span&gt;
  &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;

&lt;span class="na"&gt;deploy_cf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python:3.6&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-cf&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gce&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pip install awscli -q&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export distId=$(aws cloudfront list-distributions --output=text --query 'DistributionList.Items[*].[Id, DefaultCacheBehavior.TargetOriginId'] | grep "S3-$AWS_BUCKET" | cut -f1)&lt;/span&gt; 
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;while read -r dist; do aws cloudfront create-invalidation --distribution-id $dist --paths "/*"; done &amp;lt;&amp;lt;&amp;lt; "$distId"&lt;/span&gt;
  &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gitlab-ci file sets up two stages: deploy-s3, and deploy-cf. The first stage uploads our application to our S3 bucket, and the second invalidates the CloudFront zone for that bucket to present the new changes to our website! &lt;/p&gt;

&lt;p&gt;This simple configuration is all you need to have a complete CI/CD pipeline for your business' website. &lt;/p&gt;

&lt;p&gt;This post first appeared on our &lt;a href="https://nextlinklabs.com/insights/kubernetes-ci-cd-gitlab-with-helm" rel="noopener noreferrer"&gt;blog&lt;/a&gt; where we write about &lt;a href="https://nextlinklabs.com/services/devops-consulting-services" rel="noopener noreferrer"&gt;devops and devops consulting services&lt;/a&gt;.  &lt;/p&gt;

</description>
      <category>terraform</category>
      <category>gitlab</category>
      <category>aws</category>
      <category>devops</category>
    </item>
    <item>
      <title>Serverless contact form using AWS Lambda, API Gateway, and SES</title>
      <dc:creator>Daniel Slapelis</dc:creator>
      <pubDate>Fri, 07 May 2021 14:50:54 +0000</pubDate>
      <link>https://forem.com/danielslapelis/serverless-contact-form-using-aws-lambda-api-gateway-and-ses-3b94</link>
      <guid>https://forem.com/danielslapelis/serverless-contact-form-using-aws-lambda-api-gateway-and-ses-3b94</guid>
      <description>&lt;h2&gt;
  
  
  How to set up an email contact form using AWS Lambda, API Gateway, and SES.
&lt;/h2&gt;

&lt;p&gt;Maybe you went through my last article on &lt;a href="https://nextlinklabs.com/insights/building-cicd-pipeline-website-gitlab" rel="noopener noreferrer"&gt;how to build a CI/CD pipeline for your website using GitLab&lt;/a&gt;, or maybe you just have a website or application in an S3 bucket that you want to add a contact form for. Either way, you've come to the right article -- today I'll show you how to set up this reliable contact form using Lambda, API Gateway, and SES (and, boy, is it easy).&lt;/p&gt;

&lt;h3&gt;
  
  
  Dan, what's the point?
&lt;/h3&gt;

&lt;p&gt;When you have a traditional website on a server that uses things like Apache and PHP, you can just use &lt;a href="https://www.php.net/manual/en/function.mail.php" rel="noopener noreferrer"&gt;PHP's &lt;code&gt;mail()&lt;/code&gt;&lt;/a&gt; function if you setup an email service like Postfix. If you're serving your website from a bucket like I do, you can't exactly use Postfix (nor do I really want to because then I have to worry about the availability of another server). That's when this combination of services comes in handy!&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;There aren't too many prerequisites for this (especially if you went through my last tutorial).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An &lt;a href="https://console.aws.amazon.com/" rel="noopener noreferrer"&gt;AWS&lt;/a&gt; account&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; is installed&lt;/li&gt;
&lt;li&gt;Simple Email Service is configured in AWS for a domain or email account&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So for number 3, we need that set up because we'll need to use this service to send emails when someone fills out our contact form. I won't go through it because it's super easy, but if you need to do it, &lt;a href="https://docs.aws.amazon.com/ses/latest/DeveloperGuide/before-you-begin.html" rel="noopener noreferrer"&gt;here's the AWS documentation that you can check out&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  The infrastructure
&lt;/h4&gt;

&lt;p&gt;I went through how to do this by hand &lt;a href="https://aws.amazon.com/blogs/architecture/create-dynamic-contact-forms-for-s3-static-websites-using-aws-lambda-amazon-api-gateway-and-amazon-ses/" rel="noopener noreferrer"&gt;using this guide&lt;/a&gt;, but I always try to recreate my infrastructure with Terraform so that if I ever need it again, I can quickly use a module I have written to spin up the same thing...so luckily for you, you don't need to go through that guide! You'll just use the &lt;a href="https://gitlab.com/nextlink/lambda-api-sendmail" rel="noopener noreferrer"&gt;module I wrote&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Go ahead and create your &lt;code&gt;main.tf&lt;/code&gt; file in your Terraform project directory. Let's make it look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"las"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git::https://gitlab.com/nextlink/lambda-api-sendmail.git?ref=v1.0"&lt;/span&gt;
    &lt;span class="nx"&gt;role_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"las_role"&lt;/span&gt;
    &lt;span class="nx"&gt;function_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lambda-function-name"&lt;/span&gt;
    &lt;span class="nx"&gt;billing_tag&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"las"&lt;/span&gt;
    &lt;span class="nx"&gt;api_gateway_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"las"&lt;/span&gt;
    &lt;span class="nx"&gt;api_gateway_description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"API gateway for las"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is all the code we need to write to build our infrastructure. Can you believe it? The module is doing a lot of work behind the scenes to make our lives easier. Feel free to change those variables to whatever you need or want them to be.&lt;/p&gt;

&lt;p&gt;Besides this Terraform config, we just need to do a couple more things. Our Lambda function is defined by a file called &lt;code&gt;exports.js&lt;/code&gt;. I have a copy of it &lt;a href="https://gitlab.com/slapelis/lambda-api-sendmail/blob/master/exports.js" rel="noopener noreferrer"&gt;here&lt;/a&gt;. You should copy the source code and create a new file in your working directory called &lt;code&gt;exports.js&lt;/code&gt;. You need to do just a little bit of work, though. Lines 4 and 5 in that file look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;RECEIVER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_EMAIL@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;SENDER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-reply@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need to change the &lt;code&gt;RECEIVER&lt;/code&gt; variable to be the email you want to receive contact forms at, and &lt;code&gt;SENDER&lt;/code&gt; to be the email that you want to send emails from. This is why we set up SES earlier -- so that AWS can send emails from our domain to us.&lt;/p&gt;

&lt;p&gt;After that's all done, we have one more task to do. We need to zip up the exports file using this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zip exports.js.zip exports.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that's done, all that's left to do is to run the config.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Awesome! Our infrastructure is all set up!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/Ls6ahtmYHU760/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/Ls6ahtmYHU760/giphy.gif" alt="Dwayne Johnson nodding approvingly"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the infrastructure
&lt;/h3&gt;

&lt;p&gt;I won't get too into the HTML and JS side of this because how you do this will vary depending on what framework your website/app uses, but I want to show you what endpoint you need to call in order to send a message.&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%2Fnextlinklabs.com%2Fstatic%2F4d862813dc1587afc878b9d5497f2d4b%2Fc81eb%2Fapi-gateway.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%2Fnextlinklabs.com%2Fstatic%2F4d862813dc1587afc878b9d5497f2d4b%2Fc81eb%2Fapi-gateway.png" alt="AWS API Gateway console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Invoke URL&lt;/code&gt; in that image is what we will POST our completed form to. The only stipulation with our form (due to the &lt;code&gt;exports.js&lt;/code&gt; file we used in the Terraform config) is that we post JSON data to that url with these fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If those fields aren't what you want, you can change them back in the &lt;code&gt;exports.js&lt;/code&gt; file, zip it, and rerun the Terraform config.&lt;/p&gt;

&lt;h3&gt;
  
  
  And that's that
&lt;/h3&gt;

&lt;p&gt;With that short Terraform configuration, and a little HTML/JavaScript magic, we have an endpoint that is always available (and is really inexpensive to use) so that our users can reach out and contact us! This method is a great alternative to third-party paid solutions, and leaves a lot of room for customization. I hope this example can be of use to you in your future creations. &lt;/p&gt;

&lt;p&gt;This post first appeared on our blog where we write about &lt;a href="https://nextlinklabs.com/" rel="noopener noreferrer"&gt;devops&lt;/a&gt; and &lt;a href="https://nextlinklabs.com/services/devops-consulting-services" rel="noopener noreferrer"&gt;devops consulting services&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>lambda</category>
      <category>api</category>
    </item>
    <item>
      <title>Building a Kubernetes CI/CD Pipeline with GitLab and Helm</title>
      <dc:creator>Daniel Slapelis</dc:creator>
      <pubDate>Fri, 07 May 2021 14:10:46 +0000</pubDate>
      <link>https://forem.com/danielslapelis/building-a-kubernetes-ci-cd-pipeline-with-gitlab-and-helm-km9</link>
      <guid>https://forem.com/danielslapelis/building-a-kubernetes-ci-cd-pipeline-with-gitlab-and-helm-km9</guid>
      <description>&lt;p&gt;Everyone loves GitLab CI and Kubernetes.&lt;/p&gt;

&lt;p&gt;GitLab CI (Continuous Integration) is a popular tool for building and testing software developers write for applications. GitLab CI helps developers build code faster, more confidently, and detect errors quickly.&lt;/p&gt;

&lt;p&gt;Kubernetes, popularly shortened to K8s, is a portable, extensible, open-source platform for managing containerization workloads and services. K8s is used by companies of all sizes everyday to automate deployment, scaling, and managing applications in containers.&lt;/p&gt;

&lt;p&gt;The purpose of this post is to show how you can bolt on the Continuous Delivery (CD) piece of the puzzle to &lt;a href="https://dev.to/services/gitlab-professional-services-pipeline-healthcheck"&gt;build a CI/CD pipeline&lt;/a&gt; so you can deploy your applications to Kubernetes. But before we get too far, we're going to need to talk about Helm, which is an important part of the puzzle.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Helm?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://helm.sh"&gt;Helm calls itself "the package manager for Kubernetes".&lt;/a&gt; That's a pretty accurate description. Helm is a versatile, sturdy tool DevOps engineers can use to define configuration files in, and perform variable substitution to create consistent deployments to our clusters, and have different variables for different environments.&lt;/p&gt;

&lt;p&gt;It's certainly the right solution to the problem we're covering here.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do we do it?
&lt;/h2&gt;

&lt;p&gt;First off, a few prerequisites. You’re going to have to have this all hammered out before you started with the project. There’s links to helpful documentation below if you need help.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You already have an &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html"&gt;Amazon EKS cluster&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;You already know how to use &lt;a href="https://docs.gitlab.com/ee/ci/"&gt;GitLab CI&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;You have a &lt;a href="https://docs.gitlab.com/runner/executors/kubernetes.html"&gt;GitLab CI runner&lt;/a&gt; configured in your Kubernetes cluster.&lt;/li&gt;
&lt;li&gt;You have the &lt;a href="https://github.com/kubernetes-sigs/aws-load-balancer-controller"&gt;AWS Load Balancer Controller&lt;/a&gt; running in your cluster.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With those boxes checked, we can get started. You'll want to create a new repository in GitLab first for us to use in this example. Once you've done that we can get started with creating our files.&lt;/p&gt;

&lt;h2&gt;
  
  
  File tree
&lt;/h2&gt;

&lt;p&gt;Basically, at the end our folder/file structure is going to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dir&amp;gt;
├── chart/
|   ├── Chart.yaml
|   ├── values.yaml
|   └── templates/
|      ├── deployment.yaml
|      ├── service.yaml
|      ├── ingress.yaml
|      └── configmap.yaml
└── gitlab-ci.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  values.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;applicationName: my-first-app
certArn: your-certificate-arn
domain: your domain name
subnets: your subnets
securityGroups: your security groups
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  deployment.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.applicationName }}
  namespace: {{ .Values.applicationName  }}
spec:
  replicas: 2
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: {{ .Values.applicationName }}
  template:
    metadata:
      labels:
        app: {{ .Values.applicationName  }}
    spec:
      containers:
        - name: {{ .Values.applicationName }}
          imagePullPolicy: Always
          image: nginx:1.19.4
          ports:
            - containerPort: 80
          volumeMounts:
            - mountPath: /usr/share/nginx/html/index.html
              name: nginx-conf
              subPath: index.html
      volumes:
        - name: nginx-conf
          configMap:
            name: {{ .Values.applicationName  }}-configmap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the configuration file that defines our deployment. You can see there are a few lines with &lt;code&gt;{{ some text }}&lt;/code&gt;. This is how we use a variable we define in our values file within our chart.&lt;/p&gt;

&lt;h3&gt;
  
  
  configmap.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Values.applicationName }}-configmap
  namespace: {{ .Values.applicationName }}
data:
  index.html: |
    &amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
      &amp;lt;h1&amp;gt;My first Helm deployment!&amp;lt;/h1&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
      &amp;lt;p&amp;gt;Thanks for checking out my first Helm deployment.&amp;lt;/p&amp;gt;
    &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This config map just defines a simple index page that we'll display for our app.&lt;/p&gt;

&lt;h3&gt;
  
  
  service.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: {{ .Values.applicationName }}
  namespace: {{ .Values.applicationName }}
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
      name: {{ .Values.applicationName }}
    - port: 80
      targetPort: 80
      protocol: TCP
      name: {{ .Values.applicationName }}
  type: NodePort
  selector:
    app: {{ .Values.applicationName }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ingress.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: {{ .Values.applicationName }}
  namespace: {{ .Values.applicationName }}
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/subnets: {{ .Values.subnets }}
    alb.ingress.kubernetes.io/healthcheck-path: /
    alb.ingress.kubernetes.io/security-groups: {{ .Values.securityGroups }}
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/certificate-arn:  {{ .Values.certArn }}
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
    alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'
spec:
  rules:
    - host: {{ .Values.applicationName }}.{{ .Values.domain }}
      http:
        paths:
        - path: /*
          backend:
            serviceName: ssl-redirect
            servicePort: use-annotation
        - path: /*
          backend:
            serviceName: {{ .Values.applicationName }}
            servicePort: 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  .gitlab-ci.yml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stages:
  - deploy

variables:
  DOCKER_HOST: tcp://localhost:2375/
  DOCKER_DRIVER: overlay2
  APP_NAME: my-first-app

deploy:
  stage: deploy
  image: alpine/helm:3.2.1
  script:
    - helm upgrade ${APP_NAME} ./charts --install --values=./charts/values.yaml --namespace ${APP_NAME}
  rules:
    - if: $CI_COMMIT_BRANCH == 'master'
      when: always
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Okay we have all the files. Now what?
&lt;/h2&gt;

&lt;p&gt;Well, after you have all the files defined and your infrastructure follows our prerequisites, there's not much left to do.&lt;/p&gt;

&lt;p&gt;If you commit these files, GitLab will interpet your &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; file and initiate a pipeline. Our pipeline only has one stage and one job (deploy). It'll spin up a container in the cluster for the deployment using the &lt;code&gt;helm:3.2.1&lt;/code&gt; image and run our &lt;code&gt;script&lt;/code&gt; command. This does all of the heavy lifting for us with creating all of the files required in our namespace and starting our application.&lt;/p&gt;

&lt;p&gt;If you configure in Route53 a DNS record like &lt;code&gt;my-first-app.my-domain.com&lt;/code&gt; with an A record to the load balancer that the ingress controller created, you'll see the index page we defined in the configmap!&lt;/p&gt;

&lt;p&gt;This post first appeared on our &lt;a href="https://nextlinklabs.com/insights/kubernetes-ci-cd-gitlab-with-helm"&gt;blog&lt;/a&gt; where we write about &lt;a href="https://nextlinklabs.com/services/devops-consulting-services"&gt;devops and devops consulting services&lt;/a&gt;.  &lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>helm</category>
      <category>gitlab</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
