<?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: ChigozieCO</title>
    <description>The latest articles on Forem by ChigozieCO (@chigozieco).</description>
    <link>https://forem.com/chigozieco</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%2F887485%2Fd0a1d1a7-f8d7-47e9-8940-b97ae21f54c0.jpg</url>
      <title>Forem: ChigozieCO</title>
      <link>https://forem.com/chigozieco</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/chigozieco"/>
    <language>en</language>
    <item>
      <title>Dockerized Deployment of a Full Stack Application with Reverse Proxy, Monitoring &amp; Observability</title>
      <dc:creator>ChigozieCO</dc:creator>
      <pubDate>Sat, 04 Jan 2025 22:55:45 +0000</pubDate>
      <link>https://forem.com/chigozieco/dockerized-deployment-of-a-full-stack-application-with-reverse-proxy-monitoring-observability-5c04</link>
      <guid>https://forem.com/chigozieco/dockerized-deployment-of-a-full-stack-application-with-reverse-proxy-monitoring-observability-5c04</guid>
      <description>&lt;p&gt;The goal here is to provide a robust and scalable application infrastructure while showcasing best practices in containerization, monitoring, and cloud deployment. By the end of this project, users will have a fully functional web application deployed on a cloud platform with proper domain configuration, detailed logging, and real-time performance monitoring.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;This project demonstrates the deployment of a full-stack application with a FastAPI backend and a React frontend using Docker. It integrates a reverse proxy for routing, and implements comprehensive monitoring and observability using Prometheus, Grafana, Loki, Promtail, and cAdvisor. We will eventually host our application from a cloud platform.&lt;/p&gt;




&lt;h1&gt;
  
  
  Prerequisite
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Docker and Docker Compose&lt;/strong&gt;: Installed and configured on your system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic Knowledge of Docker&lt;/strong&gt;: Understanding containerization concepts and Docker CLI commands.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Git&lt;/strong&gt;: Installed to clone the project repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code Editor&lt;/strong&gt;: Like VS Code or any preferred IDE.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;System Requirements&lt;/strong&gt;: At least 4GB RAM and a stable internet connection for pulling images and dependencies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS Account and AWS CLI&lt;/strong&gt;: Required for deploying the application to the cloud.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Observability Tools Knowledge&lt;/strong&gt;: Familiarity with Prometheus, Grafana, Loki, cAdvisor, and Promtail for monitoring and observability setup.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Clone Application Repo
&lt;/h1&gt;

&lt;p&gt;The application we are to deploy can be found &lt;a href="https://github.com/The-DevOps-Dojo/cv-challenge01" rel="noopener noreferrer"&gt;here&lt;/a&gt;. To begin the project we will crone the repo into an empty directory using the below command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/The-DevOps-Dojo/cv-challenge01.git &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;.&lt;/code&gt; at the end of the command is to ensure that the repo is cloned directory into the directory where we are, without an additional folder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fno9lh6qu8lxplbyoto8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fno9lh6qu8lxplbyoto8e.png" alt="clone-repo" width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's begin.&lt;/p&gt;


&lt;h1&gt;
  
  
  Containerization
&lt;/h1&gt;

&lt;p&gt;This project leverages Docker to containerize the frontend, backend, database, a database management tool, a reverse proxy and monitoring and observability (Prometheus, Grafana, cAdvisor, Promtail and Loki). &lt;/p&gt;

&lt;p&gt;Using docker compose, the services are orchestrated to run seamlessly in isolated environments, enabling consistent and efficient development, testing, and deployment.&lt;/p&gt;

&lt;p&gt;Now we will go ahead and write the Dockerfile for each of the services starting from the backend, frontend, database Adminer; the and database management tool.&lt;/p&gt;
&lt;h2&gt;
  
  
  Dockerize Backend
&lt;/h2&gt;

&lt;p&gt;We will dockerize the backend now by writing the Dockerfile we will use to create our image. Navigate in the &lt;code&gt;backend&lt;/code&gt; directory and create a new file &lt;code&gt;Dockerfile&lt;/code&gt;. You can find all the code used in this project &lt;a href="https://github.com/ChigozieCO/dockerized-fullstack-observability" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Using a multi-stage Docker build I was able to reduce my image size by 70.99%, initially starting from a size of 262Mi to a final size of 76Mi as can be seen in the screenshots below:&lt;/p&gt;
&lt;h5&gt;
  
  
  Version 1
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2840825zj6b6gjz02jk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2840825zj6b6gjz02jk.png" alt="build1" width="800" height="152"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  Multi-stage Version 2 Build
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nsax8zkqhx5vaso5rr4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nsax8zkqhx5vaso5rr4.png" alt="build2" width="800" height="143"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My Dockerfile can be found &lt;a href="https://github.com/ChigozieCO/dockerized-fullstack-observability/tree/main/backend/Dockerfile" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the below code into the Dockerfile you created:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 1: Builder stage, Base image for the backend&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.10-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="c"&gt;# Install system dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl &lt;span class="se"&gt;\
&lt;/span&gt;    build-essential &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Install poetry&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://install.python-poetry.org | python3 -

&lt;span class="c"&gt;# Add poetry to PATH&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/root/.local/bin:$PATH"&lt;/span&gt;

&lt;span class="c"&gt;# Set working directory and copy only dependency files first&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /backend&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; pyproject.toml poetry.lock* ./&lt;/span&gt;

&lt;span class="c"&gt;# Install dependencies with Poetry&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;poetry config virtualenvs.create &lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; poetry &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Stage 2: Slim final image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.10-slim&lt;/span&gt;

&lt;span class="c"&gt;# Install system dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl &lt;span class="se"&gt;\
&lt;/span&gt;    postgresql-client &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Install Poetry&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://install.python-poetry.org | python3 -

&lt;span class="c"&gt;# Add poetry to PATH&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/root/.local/bin:$PATH"&lt;/span&gt;

&lt;span class="c"&gt;# Set working directory&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /backend&lt;/span&gt;

&lt;span class="c"&gt;# Copy virtual environment from builder stage&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/local/bin /usr/local/bin&lt;/span&gt;

&lt;span class="c"&gt;# Copy application files&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;poetry config virtualenvs.create &lt;span class="nb"&gt;false&lt;/span&gt;

&lt;span class="c"&gt;# Expose application port&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;

&lt;span class="c"&gt;# Set the default command to run the application&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["poetry", "run", "uvicorn", "app.main:app", "--reload", "--host", "0.0.0.0", "--port", "8000"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;✨ I opted to use the slim variant of the python image as it is lightweight while still being developer-friendly and avoids common build issues. &lt;/p&gt;

&lt;p&gt;It has better compatibility than the Alpine variant, few dependency issues, moderately sized and has better ease of debugging than Alpine does.&lt;/p&gt;

&lt;p&gt;✨ I installed curl as I would be needing it for the installation of Poetry, then installed Poetry and added it to PATH.&lt;/p&gt;

&lt;p&gt;✨ We used the &lt;code&gt;ENV&lt;/code&gt; command to set the $PATH variable globally, making sure Poetry is available in all subsequent layers and for the runtime environment.&lt;/p&gt;

&lt;p&gt;✨ Like earlier mentioned, I used a multi-stage build, in the first stage, I installed poetry and in the 2nd stage I copied environment with poetry installed to the final image, however seeing as we will still need to use poetry to set up the database with the necessary tables we will install poetry in the second stage.&lt;/p&gt;

&lt;p&gt;This has minimal impact on the size of the image so we are in the clear.&lt;/p&gt;
&lt;h3&gt;
  
  
  Build Backend Image
&lt;/h3&gt;

&lt;p&gt;We don't necessarily have to build the image but if you want to build it for testing purposes, navigate into the backend directory (where you have your backend Dockerfile) and use the command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; devopsdojo/backend:v1 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;⚠️ NOTE:&lt;/p&gt;

&lt;p&gt;Ensure docker is running before running the above command otherwise you will get an error message.&lt;/p&gt;

&lt;p&gt;To check the size of your image you can use the below command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker image inspect devopsdojo/backend:v1 &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{{.Size}}'&lt;/span&gt; | &lt;span class="nb"&gt;numfmt&lt;/span&gt; &lt;span class="nt"&gt;--to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;iec-i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;As an aside (in preparation of the next phase of this project) you can push your image to your Docker Hub repo, we will use the image from this Docker Hub repo when we want to automate this project.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Dockerize Frontend
&lt;/h2&gt;

&lt;p&gt;Just as we did with the Backend, we will write a Dockerfile that we will use in building our frontend image. Navigate into the frontend directory and create a new file &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Even though I opted to use the slim variant of the node image and also use a multi stage build as with my backend image I was not able to substantially reduce the size of my frontend image, I did manage to reduce it from over 1gb to 850mb though but I would have loved to further trim it down.&lt;/p&gt;

&lt;p&gt;This dockerfile is pretty straightforward, install npm, expose the port (merely for documentation) and start the container.&lt;/p&gt;

&lt;p&gt;Find my Frontend Dockerfile &lt;a href="https://github.com/ChigozieCO/dockerized-fullstack-observability/blob/main/frontend/Dockerfile" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Enter the below code into your Dockerfile:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Base image for the frontend&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;

&lt;span class="c"&gt;# Set working directory&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /frontend&lt;/span&gt;

&lt;span class="c"&gt;# Install debugging tools&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; bash net-tools &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Copy package.json and package-lock.json separately for better caching&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;

&lt;span class="c"&gt;# Install dependencies and clear cache&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm cache clean &lt;span class="nt"&gt;--force&lt;/span&gt;

&lt;span class="c"&gt;# Base image for stage 2 to reduce size of image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:22-slim&lt;/span&gt;

&lt;span class="c"&gt;# Set working directory&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /frontend&lt;/span&gt;

&lt;span class="c"&gt;# Copy dependencies and source code&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=base /frontend/node_modules ./node_modules&lt;/span&gt;

&lt;span class="c"&gt;# Expose application port&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 5173&lt;/span&gt;

&lt;span class="c"&gt;# Copy the rest of the application files&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Command to start the application&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "5173"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;As an aside (in preparation of the next phase of this project) you can push your image to your Docker Hub repo, we will use the image from this Docker Hub repo when we want to automate this project.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Configure Application Docker Compose file
&lt;/h2&gt;

&lt;p&gt;Docker Compose is a tool for defining and running multi-container Docker applications. It simplifies managing complex environments by using a single YAML file to configure services, networks, and volumes. It's necessary for this project to streamline the setup of interconnected services like the frontend, backend, database, database admin UI and observability, ensuring they run consistently with minimal effort.&lt;/p&gt;

&lt;p&gt;Navigate to the root directory of your project and create a new file &lt;code&gt;compose.yaml&lt;/code&gt;, here we will define the services we want to run, create a docker network that these services will use to communicate with each other and configure volumes for the services that need them.&lt;/p&gt;

&lt;p&gt;The first compose file we will create is our application compose file. In this compose file we will define the backend, frontend, database, reverse proxy (Traefik) and Adminer services.&lt;/p&gt;


&lt;h3&gt;
  
  
  First Draft of Compose File to test locally
&lt;/h3&gt;

&lt;p&gt;The below is the first draft of my docker compose file to enable me test my containers locally. Here you will see that the ports are mapped and it is missing Traefik, the reverse proxy service. This is because I wanted to test my images first before I go too far in the project.&lt;/p&gt;

&lt;p&gt;You can choose to do it like I did or skip ahead to the next configuration. &lt;/p&gt;

&lt;p&gt;Once Traefik is configured, there will no need to map the ports, like we did in this version of the docker compose file below, because Traefik will be the point of entry of all traffic to our application.&lt;/p&gt;

&lt;p&gt;If you want to test the images before moving ahead, add the below to your &lt;code&gt;compose.yaml&lt;/code&gt; file:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./frontend/.env&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./frontend&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:5173"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./backend/.env&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PYTHONPATH=/backend&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8000:8000"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;sh -c "&lt;/span&gt;
      &lt;span class="s"&gt;until pg_isready -h db -U app; do&lt;/span&gt;
        &lt;span class="s"&gt;echo 'Waiting for database...';&lt;/span&gt;
        &lt;span class="s"&gt;sleep 2;&lt;/span&gt;
      &lt;span class="s"&gt;done;&lt;/span&gt;
      &lt;span class="s"&gt;poetry run bash ./prestart.sh &amp;amp;&amp;amp; poetry run uvicorn app.main:app --host 0.0.0.0 --port 8000"&lt;/span&gt;
  &lt;span class="na"&gt;db&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;postgres:13-alpine&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=changethis123&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=app&lt;/span&gt;
    &lt;span class="c1"&gt;# ports:&lt;/span&gt;
    &lt;span class="c1"&gt;#   - "5432:5432"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db:/var/lib/postgresql/data&lt;/span&gt;
  &lt;span class="na"&gt;adminer&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;adminer&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8080:8080&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;devopsdojo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;⚠️ &lt;strong&gt;NOTE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;backend/.env&lt;/code&gt; file update your &lt;code&gt;POSTGRES_SERVER&lt;/code&gt; variable from &lt;code&gt;app&lt;/code&gt; to &lt;code&gt;db&lt;/code&gt; and if you made any other changes either to the port or the password, in your compose file, update it accordingly in the &lt;code&gt;backend/.env&lt;/code&gt; file so that both values match.&lt;/p&gt;
&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;The above setup simplifies development by isolating services while allowing them to communicate seamlessly within the same Docker network. Here’s what each service does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;frontend:&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Builds the React frontend from the &lt;code&gt;./frontend&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;Uses environment variables from &lt;code&gt;./frontend/.env&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Exposes port &lt;code&gt;5173&lt;/code&gt; internally, mapped to port &lt;code&gt;80&lt;/code&gt; on the host.&lt;/li&gt;
&lt;li&gt;Connects to the shared &lt;code&gt;devopsdojo&lt;/code&gt; network.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;backend:&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Builds the FastAPI backend from the &lt;code&gt;./backend&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;Uses environment variables from &lt;code&gt;./backend/.env&lt;/code&gt; and sets an additional PYTHONPATH for module resolution.&lt;/li&gt;
&lt;li&gt;Exposes port &lt;code&gt;8000&lt;/code&gt; for the application.&lt;/li&gt;
&lt;li&gt;Depends on the &lt;code&gt;db&lt;/code&gt; service and waits for the database to be ready before starting.&lt;/li&gt;
&lt;li&gt;Runs a prestart script to populate the database and then serves the API with Uvicorn.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;db:&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs a lightweight PostgreSQL 13-alpine database.&lt;/li&gt;
&lt;li&gt;Configured with user, password, and database names.&lt;/li&gt;
&lt;li&gt;Stores persistent data in a Docker volume (db).&lt;/li&gt;
&lt;li&gt;Connected to the shared &lt;code&gt;devopsdojo&lt;/code&gt; network.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;
  
  
  Spin up Application
&lt;/h3&gt;

&lt;p&gt;To build and run the containers use the below command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;⚠️ &lt;strong&gt;TIP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can use the &lt;code&gt;-d&lt;/code&gt; flag to run the containers in a detached mode but I like to see the logs which is why I opted not to use the &lt;code&gt;-d&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;When your containers are run you can open up &lt;code&gt;localhost&lt;/code&gt; on your browser to see the frontend (login with the super user username and password found in the backend .env file) and &lt;code&gt;localhost:8000&lt;/code&gt; and &lt;code&gt;localhost:8000/docs&lt;/code&gt; to see the backend. To view Adminer navigate to &lt;code&gt;localhost:8080&lt;/code&gt; and login with the correct credentials. You should see the below on your browser if you have correctly configured your application.&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Application Frontend&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12mognqxzb3m77y1zsxv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12mognqxzb3m77y1zsxv.png" alt="Localhost-frontend" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foiu8ubdgczcwd3pbd9cb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foiu8ubdgczcwd3pbd9cb.png" alt="lh-logged-in" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Application's Swagger UI&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2hrhxwpht1e4shyqrdb4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2hrhxwpht1e4shyqrdb4.png" alt="lh-docs" width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Adminer Dashboard&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpo7d80uy2iknhgtyx2lj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpo7d80uy2iknhgtyx2lj.png" alt="lh-adminer" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To bring down your application run the &lt;code&gt;docker compose down&lt;/code&gt; command, you can use the &lt;code&gt;-v&lt;/code&gt; flag to also remove the volume(s).&lt;/p&gt;


&lt;h1&gt;
  
  
  Cloud Deployment
&lt;/h1&gt;

&lt;p&gt;At this stage in our project we will be deploying our application to the cloud from where we will continue and finish our configuration and serve our application.&lt;/p&gt;

&lt;p&gt;We will use an EC2 instance for our deployment to keep everything simple and self managed, another reason to use EC2 is to easily run docker-compose without adapting configurations to ECS-specific constructs. &lt;/p&gt;

&lt;p&gt;We will go ahead and create an EC2 instance through the management console; if you don't know how to do it follow the instructions below:&lt;/p&gt;
&lt;h3&gt;
  
  
  Steps to Create an EC2 Instance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Log in to the AWS Management Console.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Navigate to the EC2 Dashboard under the "Compute" section.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click Launch Instance and provide a name for your instance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select an AMI (Amazon Machine Image), such as Amazon Linux 2 or Ubuntu. I'd be using the Amazon Linux.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose an instance type (e.g., t2.micro for free tier) however we will use a t2.medium as cAdvisor requires more space than is available on the t2.micro.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure a key pair for SSH access (or create a new one).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Edit network settings to allow required inbound traffic, check the boxes for SSH, HTTPS and HTTP. We will be using the default VPCs and subnets, if you don't want to you can create a new VPC.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add storage, leave the default settings as it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click Launch Instance and wait for it to initialize. Your key pair should be downloaded, note the download location&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After our instance has been launched we will SSH into it using the command below:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;path/to/your/keypair.pem&amp;gt;"&lt;/span&gt; ec2-user@&amp;lt;your public dns&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyr1wtg6uf3syn4qsh336.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyr1wtg6uf3syn4qsh336.png" alt="ssh-into-instance" width="800" height="249"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Copy project File into Instance
&lt;/h3&gt;

&lt;p&gt;Once connected to the EC2 instance we need to copy our project directory into the instance as we will be working from the instance going forward.&lt;/p&gt;

&lt;p&gt;We will use the &lt;code&gt;scp&lt;/code&gt; utility to copy our folder from our local machine to our instance using the below command.&lt;/p&gt;

&lt;p&gt;Open a new terminal (or close the connection to the instance) and enter the command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; scp &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;path/to/your/keypair.pem&amp;gt;"&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &amp;lt;path/to/your/project/directory&amp;gt; ec2-user@&amp;lt;your public dns&amp;gt;:/home/ec2-user/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb977bovy941gs5n4gu3p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb977bovy941gs5n4gu3p.png" alt="scp" width="800" height="143"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;REMINDER&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ensure you have docker and docker compose installed in your instance to continue with the project.&lt;/p&gt;

&lt;p&gt;If you need help installing Docker on an Amazon Linux EC2 instance &lt;a href="https://www.cyberciti.biz/faq/how-to-install-docker-on-amazon-linux-2/" rel="noopener noreferrer"&gt;check out this post&lt;/a&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  Configure Reverse Proxy - Traefik
&lt;/h1&gt;

&lt;p&gt;Traefik is a modern reverse proxy and load balancer designed for containerized environments. It automatically discovers services in your Docker setup and routes external traffic to the appropriate service based on configuration rules. As a reverse proxy, it sits between clients and backend services, managing requests, enhancing security, and improving performance.&lt;/p&gt;

&lt;p&gt;In this project, Traefik is the main gateway to our entire application, it handles routing for the frontend, backend, Adminer and the observability, simplifying domain management, enabling HTTPS with ease, and streamlining application deployment in the cloud. It's basically the traffic controller that decides how requests get routed to different parts of our website.&lt;/p&gt;

&lt;p&gt;Let's configure Traefik now.&lt;/p&gt;


&lt;h2&gt;
  
  
  Traefik Configuration
&lt;/h2&gt;

&lt;p&gt;The Traefik configuration file defines how Traefik operates as a reverse proxy and load balancer. It includes settings for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;EntryPoints: Specify the ports (e.g., 80 for HTTP, 443 for HTTPS) where Traefik listens for incoming requests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CertificatesResolvers: Enable automatic SSL/TLS certificate generation and renewal using Let's Encrypt.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Providers: Define where Traefik gets routing information (e.g., Docker labels, Kubernetes, or static configuration files).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Routing Rules: Configure how incoming requests are routed to specific services based on domains, subdomains, or paths.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Metrics and Observability: Optionally expose performance metrics (e.g., Prometheus) to monitor Traefik’s behavior.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To begin, create a new file &lt;code&gt;traefik.yml&lt;/code&gt; in the root of your project and add the below to the file:&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;entryPoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Entry point for HTTP traffic on port 80&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:80"&lt;/span&gt;
  &lt;span class="c1"&gt;# Entry point for HTTPS traffic on port 443&lt;/span&gt;
  &lt;span class="na"&gt;websecure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:443"&lt;/span&gt;

&lt;span class="c1"&gt;# Configuration for obtaining SSL certificates via Let's Encrypt&lt;/span&gt;
&lt;span class="na"&gt;certificatesResolvers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;myresolver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;acme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Email address for Let's Encrypt notifications and account management&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;your email address&amp;gt;&lt;/span&gt;
      &lt;span class="c1"&gt;# Storage file for SSL certificates&lt;/span&gt;
      &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;acme.json&lt;/span&gt;
      &lt;span class="c1"&gt;# Use HTTP challenge for domain verification&lt;/span&gt;
      &lt;span class="na"&gt;httpChallenge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;entryPoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;

&lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Configure routers for handling requests&lt;/span&gt;
  &lt;span class="na"&gt;routers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Global HTTP to HTTPS redirection&lt;/span&gt;
    &lt;span class="na"&gt;redirect-to-https&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Match all HTTP requests with any hostname&lt;/span&gt;
      &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HostRegexp(`{host:.+}`)"&lt;/span&gt; 
      &lt;span class="na"&gt;entryPoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt; 
      &lt;span class="c1"&gt;# Middleware to handle the redirection&lt;/span&gt;
      &lt;span class="na"&gt;middlewares&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redirect-to-https&lt;/span&gt; 
      &lt;span class="c1"&gt;# No actual service, just a placeholder for redirection&lt;/span&gt;
      &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;noop@internal&lt;/span&gt;

    &lt;span class="c1"&gt;# Router to redirect www to non-www&lt;/span&gt;
    &lt;span class="na"&gt;redirect-www&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Host(`&amp;lt;your&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;domain&amp;gt;`)"&lt;/span&gt;
      &lt;span class="na"&gt;entryPoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;websecure&lt;/span&gt;
      &lt;span class="c1"&gt;# Middleware to handle the redirection&lt;/span&gt;
      &lt;span class="na"&gt;middlewares&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redirect-www&lt;/span&gt;
      &lt;span class="c1"&gt;# No actual service, just a placeholder for redirection&lt;/span&gt;
      &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;noop@internal&lt;/span&gt;

  &lt;span class="na"&gt;middlewares&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Middleware to redirect HTTP to HTTPS&lt;/span&gt;
    &lt;span class="na"&gt;redirect-to-https&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;redirectScheme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https&lt;/span&gt;
        &lt;span class="c1"&gt;# Sends a permanent redirect (HTTP 301)&lt;/span&gt;
        &lt;span class="na"&gt;permanent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

    &lt;span class="c1"&gt;# Middleware to redirect 'www' subdomain to the root domain&lt;/span&gt;
    &lt;span class="na"&gt;redirect-www&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;redirectRegex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Matches URLs starting with 'www.'&lt;/span&gt;
        &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^https?://www&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;.(.*)"&lt;/span&gt;
        &lt;span class="c1"&gt;# Replaces 'www.' with the root domain&lt;/span&gt;
        &lt;span class="na"&gt;replacement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://$1"&lt;/span&gt;
        &lt;span class="c1"&gt;# Sends a permanent redirect (HTTP 301)&lt;/span&gt;
        &lt;span class="na"&gt;permanent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;# Enable Prometheus metrics for monitoring&lt;/span&gt;
&lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Add labels for entry point and service&lt;/span&gt;
    &lt;span class="na"&gt;addEntryPointsLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;addServicesLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;# Use Docker as the provider for Traefik configurations&lt;/span&gt;
&lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Only explicitly exposed containers will be served by Traefik&lt;/span&gt;
    &lt;span class="na"&gt;exposedByDefault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;This traefik configuration serves as the smart traffic manager for our website. It does a few key things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Sets up two main entry points: Port 80 (regular web traffic) and Port 443 (secure, encrypted traffic).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automatically gets free SSL certificates from Let's Encrypt, keeping our site secure without manual certificate hunting. It uses your email specified in the configuration for certificate management.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automatically sends all HTTP traffic to HTTPS (no unsecured connections), redirects &lt;a href="http://www.yourdomain.com" rel="noopener noreferrer"&gt;www.yourdomain.com&lt;/a&gt; to yourdomain.com and ensures visitors always reach the right version of your site.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adds tracking labels for Prometheus (helps you watch how your site is performing).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Only serves containers you explicitly tell it to expose.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Docker Compose Configuration for Traefik
&lt;/h2&gt;

&lt;p&gt;We need to update our compose file with the reverse-proxy service and add the necessary Traefik labels to our other services. Your &lt;code&gt;compose.yaml&lt;/code&gt; should now 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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./frontend/.env&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./frontend&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
    &lt;span class="c1"&gt;# ports:&lt;/span&gt;
    &lt;span class="c1"&gt;#   - "80:5173"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Enable Traefik for this service and specify the secure entrypoint (HTTPS)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.enable=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.frontend.rule=Host(`&amp;lt;yourdomain&amp;gt;`)"&lt;/span&gt; &lt;span class="c1"&gt;# substitute with your domain name&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.frontend.entrypoints=websecure"&lt;/span&gt;

      &lt;span class="c1"&gt;# Enable TLS for this router &amp;amp; use the 'myresolver' certificates resolver for obtaining SSL certificates&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.frontend.tls=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.frontend.tls.certresolver=myresolver"&lt;/span&gt;

  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./backend/.env&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PYTHONPATH=/backend&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="c1"&gt;# ports:&lt;/span&gt;
    &lt;span class="c1"&gt;#   - "8000:8000"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;sh -c "&lt;/span&gt;
      &lt;span class="s"&gt;until pg_isready -h db -U app; do&lt;/span&gt;
        &lt;span class="s"&gt;echo 'Waiting for database...';&lt;/span&gt;
        &lt;span class="s"&gt;sleep 2;&lt;/span&gt;
      &lt;span class="s"&gt;done;&lt;/span&gt;
      &lt;span class="s"&gt;poetry run bash ./prestart.sh &amp;amp;&amp;amp; poetry run uvicorn app.main:app --host 0.0.0.0 --port 8000"&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Enable Traefik for this service and specify the secure entrypoint (HTTPS)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.enable=true"&lt;/span&gt;
      &lt;span class="c1"&gt;# # Middleware for CORS&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.middlewares.backend-cors.headers.accessControlAllowOriginList=https://&amp;lt;yourdomain&amp;gt;"&lt;/span&gt; &lt;span class="c1"&gt;# substitute with your domain name&lt;/span&gt;
      &lt;span class="c1"&gt;# Route /api to the backend root&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.backend-api.rule=Host(`&amp;lt;yourdomain&amp;gt;`)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PathPrefix(`/api`)"&lt;/span&gt; &lt;span class="c1"&gt;# substitute with your domain name&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.middlewares.api-strip-prefix.stripPrefix.prefixes=/api"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.backend-api.middlewares=api-strip-prefix,backend-cors"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.backend-api.entrypoints=websecure"&lt;/span&gt;

      &lt;span class="c1"&gt;# Route /docs to /docs (Swagger UI)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.backend-docs.rule=Host(`&amp;lt;yourdomain&amp;gt;`)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PathPrefix(`/docs`)"&lt;/span&gt; &lt;span class="c1"&gt;# substitute with your domain name&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.backend-docs.middlewares=backend-cors"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.backend-docs.entrypoints=websecure"&lt;/span&gt;

      &lt;span class="c1"&gt;# Route /api/v1/openapi.json to the OpenAPI spec&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.backend-openapi.rule=Host(`&amp;lt;yourdomain&amp;gt;`)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Path(`/api/v1/openapi.json`)"&lt;/span&gt; &lt;span class="c1"&gt;# substitute with your domain name&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.backend-openapi.middlewares=backend-cors"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.backend-openapi.entrypoints=websecure"&lt;/span&gt;

      &lt;span class="c1"&gt;# Enable TLS for these routers &amp;amp; use the 'myresolver' certificates resolver for obtaining SSL certificates&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.backend-api.tls=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.backend-api.tls.certresolver=myresolver"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.backend-docs.tls=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.backend-docs.tls.certresolver=myresolver"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.backend-openapi.tls=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.backend-openapi.tls.certresolver=myresolver"&lt;/span&gt;

  &lt;span class="na"&gt;db&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;postgres:13-alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=changethis123&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=app&lt;/span&gt;
    &lt;span class="c1"&gt;# ports:&lt;/span&gt;
    &lt;span class="c1"&gt;#   - "5432:5432"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db:/var/lib/postgresql/data&lt;/span&gt;

  &lt;span class="na"&gt;adminer&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;adminer&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;adminer&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="c1"&gt;# ports:&lt;/span&gt;
    &lt;span class="c1"&gt;#   - 8080:8080&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Enable Traefik for this service and specify the secure entrypoint (HTTPS)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.enable=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.adminer.rule=Host(`db.&amp;lt;yourdomain&amp;gt;`)"&lt;/span&gt; &lt;span class="c1"&gt;# substitute with your domain name&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.adminer.entrypoints=websecure"&lt;/span&gt;
      &lt;span class="c1"&gt;# Enable TLS for this router &amp;amp; use the 'myresolver' certificates resolver for obtaining SSL certificates&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.adminer.tls=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.adminer.tls.certresolver=myresolver"&lt;/span&gt;

  &lt;span class="na"&gt;reverse-proxy&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;traefik:v3.2&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--api&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/run/docker.sock:/var/run/docker.sock:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./traefik.yml:/etc/traefik/traefik.yml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./acme.json:/acme.json&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Enable Traefik for this service&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.enable=true"&lt;/span&gt;

      &lt;span class="c1"&gt;# Dashboard route&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.dashboard-api.rule=Host(`&amp;lt;yourdomain&amp;gt;`)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PathPrefix(`/dashboard`)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(PathPrefix(`/debug`)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PathPrefix(`/api/http`)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PathPrefix(`/api/tcp`)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PathPrefix(`/api/udp`)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PathPrefix(`/api/entrypoints`)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PathPrefix(`/api/overview`)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PathPrefix(`/api/rawdata`)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PathPrefix(`/api/version`))"&lt;/span&gt; &lt;span class="c1"&gt;# substitute with your domain name&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.dashboard-api.service=api@internal"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.middlewares.dashboard-auth.basicauth.users=admin:&amp;lt;hashed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;password&amp;gt;"&lt;/span&gt; &lt;span class="c1"&gt;# Substitute with your username:hashed-password you generated with htpasswd&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.dashboard-api.middlewares=dashboard-auth,redirect-dashboard"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.dashboard-api.entrypoints=websecure"&lt;/span&gt;

      &lt;span class="c1"&gt;# Enable TLS for this router &amp;amp; use the 'myresolver' certificates resolver for obtaining SSL certificates&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.dashboard-api.tls=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.dashboard-api.tls.certresolver=myresolver"&lt;/span&gt;

      &lt;span class="c1"&gt;# Redirect /dashboard to /dashboard/&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.middlewares.redirect-dashboard.redirectregex.regex=^https?://(.*)/dashboard$$"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.middlewares.redirect-dashboard.redirectregex.replacement=https://$1/dashboard/"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.middlewares.redirect-dashboard.redirectregex.permanent=true"&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;devopsdojo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Updates Made
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We added the necessary labels to all our services to allow traefik route traffic to those services, the only service that is exempted is the db service because we will only access the database via Adminer, our database administration service.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We removed the port mappings which we had added to our services to enable us test our application locally. This is necessary because all the traffic to our application will be routed through Traefik.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We added the reverse-proxy service; the configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Opens ports 80 (HTTP), 443 (HTTPS), and 8080 (Traefik dashboard).&lt;/li&gt;
&lt;li&gt;Allows incoming web traffic and Traefik management.&lt;/li&gt;
&lt;li&gt;Enables the Traefik dashboard at /dashboard. Our dashboard uses authentication so that unauthorized persons do not have access to our dashboard. (I will show you how to hash your password soon)&lt;/li&gt;
&lt;li&gt;Ensures the dashboard is only accessible via HTTPS.&lt;/li&gt;
&lt;li&gt;The last update we made was to redirect traffic from &lt;code&gt;/dashboard&lt;/code&gt; to &lt;code&gt;/dashboard/&lt;/code&gt; as traefik expects the trailing backslash to ensure proper routing and resource loading for certain applications, like the dashboard. Without the trailing slash, API calls or static resource paths may fail, leading to incomplete or broken functionality. This redirect guarantees consistency and resolves such issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;
  
  
  Generate Password Hash
&lt;/h3&gt;

&lt;p&gt;To generate the hashed password, you can use an htpasswd generator online or use the &lt;code&gt;htpasswd&lt;/code&gt; command but before using the command ensure you install it using the below command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install apache2-utils if not already installed&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;apache2-utils

&lt;span class="c"&gt;# Or if using an Amazon Linux server like me use the below command &lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;httpd-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;⚠️ NOTE&lt;/p&gt;

&lt;p&gt;When used in a docker compose file, all dollar signs in the hash need to be doubled for escaping. This is the reason for the sed command used in the command below.&lt;/p&gt;

&lt;p&gt;Generate that password hash using the command below:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;htpasswd &lt;span class="nt"&gt;-nbB&lt;/span&gt; admin &amp;lt;yourpassword&amp;gt;&lt;span class="si"&gt;)&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; s/&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="nv"&gt;$/&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="nv"&gt;$\&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;/g
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fub9sfdcop465wi3xh9y7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fub9sfdcop465wi3xh9y7.png" alt="hashed-password" width="800" height="34"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The output will give you the &lt;code&gt;username:hashed-password&lt;/code&gt; format to use in the configuration. Copy the output and substitute it into the &lt;code&gt;dashboard-auth middleware&lt;/code&gt; label in the reverse-proxy service in your compose file. &lt;/p&gt;


&lt;h2&gt;
  
  
  Prepare the &lt;code&gt;acme.json&lt;/code&gt; File
&lt;/h2&gt;

&lt;p&gt;If you noticed, the reverse-proxy service has an &lt;code&gt;acme.json&lt;/code&gt; volume where the SSL certificate will be saved. This file needs to already exist in our project directory (same directory where your compose file is) and be writeable so that traefik can save our SSL certificate there so we need to create that file.&lt;/p&gt;

&lt;p&gt;Use the command below:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;acme.json
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ./acme.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We will not commit this file to version control but we will encrypt the file and commit the encrypted version so that whenever we are working on the project we will decrypt it and use it. &lt;/p&gt;

&lt;p&gt;Doing this allows seamless reuse of the certificates across projects, preventing unnecessary certificate renewals and avoiding Let's Encrypt rate limits. &lt;/p&gt;

&lt;p&gt;Add the &lt;code&gt;acme.json&lt;/code&gt; file to &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once we are done for the day we will encrypt the &lt;code&gt;acme.json&lt;/code&gt; file with &lt;code&gt;sops&lt;/code&gt; and push that encrypted version to version control.&lt;/p&gt;
&lt;h3&gt;
  
  
  Encrypt and Decrypt Acme File
&lt;/h3&gt;

&lt;p&gt;⚠️ ⚠️ NOTE: Skip this step, only implement it when you already have your SSL certificate and want to commit your project to version control.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Install &lt;code&gt;sops&lt;/code&gt;:&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;sops is simple, integrates with version control, and supports multiple encryption backends (e.g., AWS KMS, GCP KMS, Azure Key Vault, or a GPG key).&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# For Ubuntu/Debian&lt;/span&gt;
&lt;span class="nv"&gt;SOPS_LATEST_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://api.github.com/repos/getsops/sops/releases/latest"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Po&lt;/span&gt; &lt;span class="s1"&gt;'"tag_name": "v\K[0-9.]+'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
curl &lt;span class="nt"&gt;-Lo&lt;/span&gt; sops.deb &lt;span class="s2"&gt;"https://github.com/getsops/sops/releases/download/v&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SOPS_LATEST_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sops_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SOPS_LATEST_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_amd64.deb"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nt"&gt;--fix-broken&lt;/span&gt; &lt;span class="nb"&gt;install&lt;/span&gt; ./sops.deb
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; sops.deb

&lt;span class="c"&gt;# For macOS (Homebrew)&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;sops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Encrypt with Age Key:&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First will install age before we are able to generate a key with age:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# On macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;age

&lt;span class="c"&gt;# On Ubuntu/Debian&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;age

&lt;span class="c"&gt;# On Windows with Chocolatey&lt;/span&gt;
choco &lt;span class="nb"&gt;install &lt;/span&gt;age.portable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then we will generate an Age key pair which we will use for our encryption:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a directory for the key&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.config/sops/age

&lt;span class="c"&gt;# Generate the key&lt;/span&gt;
age-keygen &lt;span class="nt"&gt;-o&lt;/span&gt; ~/.config/sops/age/keys.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Important: Keep your Keypair file secure, we will have to save it as an env var to use for CI/CD. Do not commit the private key to version control.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we can encrypt the file using the public key from our generate keypair:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Extract public key from keys.txt&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AGE_PUBLIC_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"&lt;/span&gt;

&lt;span class="c"&gt;# Encrypt your file&lt;/span&gt;
sops &lt;span class="nt"&gt;--encrypt&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--age&lt;/span&gt; &lt;span class="nv"&gt;$AGE_PUBLIC_KEY&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; acme.json.enc &lt;span class="se"&gt;\&lt;/span&gt;
  acme.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;To Decrypt Files Locally:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# SOPS will automatically look for the key in ~/.config/sops/age/keys.txt&lt;/span&gt;
sops &lt;span class="nt"&gt;--decrypt&lt;/span&gt; acme.json.enc &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; acme.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create DNS (A) Record for Domains
&lt;/h2&gt;

&lt;p&gt;DNS records are like the postal address for your website, telling the internet exactly where to find your digital home. When you create these records, you're essentially setting up a precise navigation system that directs internet traffic to your specific server. &lt;/p&gt;

&lt;p&gt;These records are crucial because they enable services like Let's Encrypt to verify your domain ownership, allow automatic SSL certificate generation, and ensure that when someone types your domain name, they're routed to the correct IP address. &lt;/p&gt;

&lt;p&gt;Think of it as creating a map that guides visitors directly to your online doorstep, making sure they arrive safely, securely, and exactly where you want them to be.&lt;/p&gt;

&lt;p&gt;Depending on your domain hosting service, the process to create these records may differ; however you need to create an A record for &lt;code&gt;your domain&lt;/code&gt;, &lt;code&gt;db.&amp;lt;yourdomain&amp;gt;&lt;/code&gt; and &lt;code&gt;www.&amp;lt;your domain&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;⚠️ NOTE&lt;/p&gt;

&lt;p&gt;Ensure you create these records, if they are not created your application will not be served on your domain.&lt;/p&gt;

&lt;p&gt;Use the public IP address of your EC2 instance created earlier for the A record.&lt;/p&gt;


&lt;h2&gt;
  
  
  Update your Environments
&lt;/h2&gt;

&lt;p&gt;In both your backend and frontend directories you need to update the &lt;code&gt;.env&lt;/code&gt; files to include your domain to avoid CORS issue and so that your application is accessible from your domain.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;backend/.env&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Update the &lt;code&gt;DOMAIN&lt;/code&gt; and &lt;code&gt;BACKEND_CORS_ORIGINS&lt;/code&gt; variables in your &lt;code&gt;backend/.env&lt;/code&gt; to include your domain, it should look like this now:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;yourdomain&amp;gt; &lt;span class="c"&gt;# This has no leading http or https eg example.com&lt;/span&gt;

&lt;span class="nv"&gt;BACKEND_CORS_ORIGINS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost,http://localhost:5173,https://localhost,https://localhost:5173,http://&amp;lt;yourdomain&amp;gt;,https://&amp;lt;yourdomain&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;frontend/.env&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Update &lt;code&gt;VITE_API_URL&lt;/code&gt; with your domain name, it should now look like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;VITE_API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://&amp;lt;yourdomain&amp;gt;/api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now we are set to rebuild our image and test our traefik configuration.&lt;/p&gt;


&lt;h2&gt;
  
  
  Build Application
&lt;/h2&gt;

&lt;p&gt;After you have made all these adjustments, you are set to build the containers and run your application, do this by simply running the Docker compose up command as seen below, we will use the &lt;code&gt;-d&lt;/code&gt; flag to run it in a detached mode, if you would like to see the logs you can omit the -d flag.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You should now be able to access your application from your domain as seen in the images below.&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Application Frontend&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frj7ixyqsi68f9ityoute.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frj7ixyqsi68f9ityoute.png" alt="domain-frontend" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5j5sqcdu3xjol18z9vc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5j5sqcdu3xjol18z9vc.png" alt="domain-logged-in" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Application Backend Root&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz2dqo29uueigmeq4tfhv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz2dqo29uueigmeq4tfhv.png" alt="domain/api" width="737" height="319"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Application's Swagger UI&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqqzdnz0t1dktxvw37df.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqqzdnz0t1dktxvw37df.png" alt="domain/docs" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Adminer Dashboard&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frx7tpwt1fqijhp5de4yc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frx7tpwt1fqijhp5de4yc.png" alt="db/domain" width="800" height="321"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Traefik Dashboard Asking for Authentication&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7iyom6d9lx32yuu995i8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7iyom6d9lx32yuu995i8.png" alt="auth-traefik" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you do not supply an credentials or you enter a wrong username or password you won't be granted access as seen in the image below.&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Unauthorized Response From Traefik Dashboard&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgb34oqraleyld6hhwlw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgb34oqraleyld6hhwlw.png" alt="traefik-unauthorized" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Traefik Dashboard&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;If you successfully authenticate with the correct credentials you should see a dashboard that resembles the one below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqlhtz8jcm9wem32ug1o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqlhtz8jcm9wem32ug1o.png" alt="traefik-dash" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  Monitoring and Observability
&lt;/h1&gt;

&lt;p&gt;We're almost at the end of this project, we need to configure our monitoring and observability stack. Effective monitoring and observability are critical for maintaining the health and performance of modern applications. In this project, we implement a robust monitoring stack using Prometheus, Grafana, Loki, Promtail, and cAdvisor to ensure real-time visibility into system metrics, logs, and container performance.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Prometheus: Collects and stores metrics, enabling detailed insights into application and infrastructure performance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Grafana: Visualizes metrics and logs through customizable dashboards.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Loki: Provides log aggregation and querying capabilities.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Promtail: Streams logs from application containers to Loki.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;cAdvisor: Monitors resource usage and performance of running containers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, these tools create an integrated solution for proactive monitoring, streamlined troubleshooting, and maintaining operational excellence in containerized environments.&lt;/p&gt;


&lt;h2&gt;
  
  
  Configure Prometheus
&lt;/h2&gt;

&lt;p&gt;Due to its robust querying language, effective storage, and simplicity in integrating with several metrics sources, Prometheus is a popular open-source monitoring and alerting solution.&lt;/p&gt;

&lt;p&gt;✨&lt;/p&gt;
&lt;h3&gt;
  
  
  Docker Compose Configuration for Prometheus
&lt;/h3&gt;

&lt;p&gt;We will separate our application stack from our monitoring stack and so we need to create a new compose file, in your project root create a new file &lt;code&gt;compose.monitoring.yaml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;compose.monitoring.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Add the below in the new file:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&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;prom/prometheus&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prometheus&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--config.file=/etc/prometheus/prometheus.yml'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--web.external-url=/prometheus'&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./prometheus.yml:/etc/prometheus/prometheus.yml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prom_data:/prometheus&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Enable Traefik for this service, configure router and specify entrypoint&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.enable=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.prometheus.rule=Host(`&amp;lt;yourdomain&amp;gt;`)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PathPrefix(`/prometheus`)"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.prometheus.entrypoints=websecure"&lt;/span&gt;

      &lt;span class="c1"&gt;# Tell Traefik to use the port 9090 to connect to prometheus&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.services.prometheus.loadbalancer.server.url=http://prometheus:9090/"&lt;/span&gt;

      &lt;span class="c1"&gt;# Enable TLS for this router &amp;amp; use the 'myresolver' certificates resolver for obtaining SSL certificates&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.prometheus.tls=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.prometheus.tls.certresolver=myresolver"&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prom_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;devopsdojo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;✨ This Docker Compose setup deploys Prometheus in a Docker container, along with a specified configuration file for custom monitoring settings. &lt;/p&gt;

&lt;p&gt;✨ This Prometheus service is configured to run in a Docker container with the &lt;code&gt;prom/prometheus&lt;/code&gt; image. It uses a custom configuration file &lt;code&gt;prometheus.yml&lt;/code&gt;, which we will create and populate next, and persists data in a named volume &lt;code&gt;prom_data&lt;/code&gt;. It connects to the devopsdojo network and integrates with Traefik for secure access. &lt;/p&gt;

&lt;p&gt;✨ Traefik routes traffic to Prometheus via websecure and TLS, using the domain you specify with the path &lt;code&gt;/prometheus&lt;/code&gt;. The service listens on port 9090, but since we will access the prometheus dashboard via a subpath routed through Traefik we need to explicitly make the prometheus service aware of this subpath by explicitly appending the subpath to the target server URL in it's loadBalancer configuration as you can see that we did above.&lt;/p&gt;

&lt;p&gt;Next we create the prometheus configuration file that instructs prometheus on what to do.&lt;/p&gt;


&lt;h3&gt;
  
  
  Prometheus Configuration File
&lt;/h3&gt;

&lt;p&gt;The Prometheus configuration file is a YAML-based document that outlines how Prometheus should scrape, collect, and process metrics from various targets. &lt;/p&gt;

&lt;p&gt;It defines parameters such as scrape intervals, targets to scrape, and rules for alerting, providing the blueprint for effective monitoring setups. The configuration file is the core of the Prometheus setup and is crucial for accurate and efficient monitoring. It's human-readable and easy to edit.&lt;/p&gt;

&lt;p&gt;In your project root create a new file &lt;code&gt;prometheus.yml&lt;/code&gt; and add the below blocks of code to the file:&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="c1"&gt;# Global defaults, applies to all scrape jobs unless explicitly overridden&lt;/span&gt;
&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scrape_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15s&lt;/span&gt;
  &lt;span class="na"&gt;scrape_timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
  &lt;span class="na"&gt;evaluation_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15s&lt;/span&gt;

&lt;span class="c1"&gt;# Define the specify endpoints prometheus should scrape data from&lt;/span&gt;
&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Config to scrape data from the prometheus service itself&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prometheus'&lt;/span&gt;
    &lt;span class="na"&gt;honor_timestamps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;metrics_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prometheus/metrics&lt;/span&gt;
    &lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prometheus:9090'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# Config to scrape data from the traefik service&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;traefik'&lt;/span&gt;
    &lt;span class="na"&gt;metrics_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/metrics&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;traefik:8080'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This Prometheus configuration does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Global Settings: These settings apply to all scrape jobs unless overridden. They define default behavior for how Prometheus scrapes metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;scrape_interval&lt;/code&gt;: Frequency of scraping. Sets the default time between each scrape (15 seconds).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scrape_timeout&lt;/code&gt;: Maximum time Prometheus waits for a scrape to complete. Specifies how long Prometheus should wait for a scrape to complete (10 seconds).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;evaluation_interval&lt;/code&gt;: Defines how often Prometheus evaluates alerting and recording rules (15 seconds).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✨&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Scrape Configurations: Defines how Prometheus scrapes data from specific services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Prometheus Job&lt;/code&gt;: Scrapes Prometheus's own metrics at prometheus:9090/prometheus/metrics.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;honor_timestamps&lt;/code&gt;: Ensures that scraped data honors the timestamps from the source.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Traefik Job&lt;/code&gt;: Scrapes metrics from the Traefik service at traefik:8080/metrics.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both jobs define their targets and metrics paths for scraping, ensuring that Prometheus collects data from the specified services.&lt;/p&gt;

&lt;p&gt;We will still come back to this configuration when we setup cAdvisor so we can add a job to scrape it's data.&lt;/p&gt;


&lt;h2&gt;
  
  
  Configure cAdvisor
&lt;/h2&gt;

&lt;p&gt;Google created cAdvisor (Container Advisor), a tool that allows for real-time tracking of performance metrics and resource utilization for containers in use. It gathers, compiles, analyses, and exports data about containers that are currently running so that Prometheus can use it for monitoring.&lt;/p&gt;

&lt;p&gt;We will create the cadvisor service in docker compose now by adding the below block of code into the &lt;code&gt;compose.monitoring.yaml&lt;/code&gt; file:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;compose.monitoring.yaml&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;cadvisor&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;gcr.io/cadvisor/cadvisor&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cadvisor&lt;/span&gt;
    &lt;span class="na"&gt;privileged&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/:/rootfs:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/run:/var/run:rw&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/sys:/sys:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/lib/docker/:/var/lib/docker:ro&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now we need to tell Prometheus to scrape data from cAdvisor, to do that add the below block of code to the &lt;code&gt;prometheus.yml&lt;/code&gt; file&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;prometheus.yml&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="c1"&gt;# Config to scrape data from the cAdvisor service&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cadvisor'&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cadvisor:8080'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure Loki
&lt;/h2&gt;

&lt;p&gt;Loki is a log aggregation system designed by Grafana Labs for efficiently collecting, storing, and querying logs. Unlike traditional logging systems, Loki is optimized for cost-efficiency and simplicity by indexing only metadata, not the content of logs. It's often paired with Promtail, which collects logs from various sources (e.g., Docker containers, system logs) and pushes them to Loki. Together with Grafana, Loki provides a powerful, scalable solution for centralized log management and visualization.&lt;/p&gt;


&lt;h3&gt;
  
  
  Docker Compose Configuration for Loki
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;compose.monitoring.yaml&lt;/code&gt; file add the following after the cadvisor service:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;&lt;code&gt;compose.monitoring.yaml&lt;/code&gt;&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;loki&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;grafana/loki:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;loki&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--config.file=/etc/loki/loki-config.yaml'&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./loki-config.yaml:/etc/loki/loki-config.yaml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;loki-data:/loki&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Enable Traefik for this service, configure router and specify entrypoint&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.enable=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.loki.rule=Host(`&amp;lt;yourdomain&amp;gt;`)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PathPrefix(`/loki`)"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.loki.entrypoints=websecure"&lt;/span&gt;

      &lt;span class="c1"&gt;# Tell Traefik to use the port 3100 to connect to loki&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.services.loki.loadbalancer.server.url=http://loki:3100/"&lt;/span&gt;

      &lt;span class="c1"&gt;# Add middleware to strip the prefix&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.middlewares.loki-strip.stripprefix.prefixes=/loki"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.loki.middlewares=loki-strip"&lt;/span&gt;

      &lt;span class="c1"&gt;# Enable TLS for this router &amp;amp; use the 'myresolver' certificates resolver for obtaining SSL certificates&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.loki.tls=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.loki.tls.certresolver=myresolver"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Add the Loki volume to the Volume top level element, your volume should look like this now:&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;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prom_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;loki-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Loki Configuration File
&lt;/h3&gt;

&lt;p&gt;If you noticed, in docker compose configuration above we specified a config file but we do not have any config file yet and so the next step is to create that file.&lt;/p&gt;

&lt;p&gt;We need to download the necessary configuration files for Loki, to do this run the below command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://raw.githubusercontent.com/grafana/loki/v3.0.0/cmd/loki/loki-local-config.yaml &lt;span class="nt"&gt;-O&lt;/span&gt; loki-config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We will leave the config file as is as the configuration is sufficient for what we need to do.&lt;/p&gt;


&lt;h2&gt;
  
  
  Configure Promtail
&lt;/h2&gt;

&lt;p&gt;Promtail is an agent designed to collect and forward logs to Loki. It works seamlessly with various log sources, including system logs, application logs, and Docker container logs. &lt;/p&gt;

&lt;p&gt;Promtail reads log files, adds metadata such as labels (e.g., container name, job, or hostname), and sends the enriched logs to Loki for aggregation and querying. It integrates natively with Kubernetes, leveraging pod labels and annotations to simplify log collection in containerized environments. &lt;/p&gt;

&lt;p&gt;Promtail is lightweight, easy to configure, and essential for building a robust logging pipeline with Loki.&lt;/p&gt;


&lt;h3&gt;
  
  
  Docker Compose Configuration for Promtail
&lt;/h3&gt;

&lt;p&gt;Add the below to your &lt;code&gt;compose.monitoring.yaml&lt;/code&gt; file&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;&lt;code&gt;compose.monitoring.yaml&lt;/code&gt;&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;promtail&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;grafana/promtail:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;promtail&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--config.file=/etc/promtail/config.yml'&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./promtail-config.yml:/etc/promtail/config.yml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/lib/docker/containers:/var/lib/docker/containers:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/log:/var/log:ro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Promtail Configuration File
&lt;/h3&gt;

&lt;p&gt;Like we did with the Loki configuration, we need to download the configuration file for Promtail, do that by running the below command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://raw.githubusercontent.com/grafana/loki/v3.0.0/clients/cmd/promtail/promtail-docker-config.yaml &lt;span class="nt"&gt;-O&lt;/span&gt; promtail-config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We will update the promtail configuration file we just downloaded, we need to update our client url and add another job to the configuration.&lt;/p&gt;

&lt;p&gt;Update your &lt;code&gt;client: - url&lt;/code&gt; to look like the below:&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;clients&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://&amp;lt;your domain&amp;gt;/loki/loki/api/v1/push&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After the system job at the end of the file, add the docker job:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
        &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;
          &lt;span class="na"&gt;__path__&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/lib/docker/containers/*/*.log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure Grafana
&lt;/h2&gt;

&lt;p&gt;Next we will configure our Grafana service so that we can visualize our data via Grafana's dashboard. Grafana enables you to query, visualize, alert on, and explore your metrics, logs, and traces wherever they’re stored. Grafana data source plugins enable you to query data sources including time series databases like Prometheus and CloudWatch, logging tools like Loki and Elasticsearch and a lot more.&lt;/p&gt;

&lt;p&gt;Grafana OSS provides you with tools to display that data on live dashboards with insightful graphs and visualizations.&lt;/p&gt;


&lt;h3&gt;
  
  
  Docker Compose Configuration for Grafana
&lt;/h3&gt;

&lt;p&gt;For our Docker compose configuration of our Grafana service, add  the below to your &lt;code&gt;compose.monitoring.yaml&lt;/code&gt; file&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;grafana&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;grafana/grafana&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GF_SERVER_DOMAIN=&amp;lt;yourdomain&amp;gt;"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GF_SERVER_ROOT_URL=%(protocol)s://%(domain)s/grafana"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GF_SERVER_SERVE_FROM_SUB_PATH=true"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;devopsdojo&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;grafana-storage:/var/lib/grafana&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Enable Traefik for this service, configure router and specify entrypoint&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.enable=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.grafana.rule=Host(`&amp;lt;yourdomain&amp;gt;`)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PathPrefix(`/grafana`)"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.grafana.entrypoints=websecure"&lt;/span&gt;

      &lt;span class="c1"&gt;# Tell Traefik to use the port 3000 to connect to grafana&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.services.grafana.loadbalancer.server.url=http://grafana:3000"&lt;/span&gt;

      &lt;span class="c1"&gt;# Enable TLS for this router &amp;amp; use the 'myresolver' certificates resolver for obtaining SSL certificates&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.grafana.tls=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.grafana.tls.certresolver=myresolver"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The volume part of the compose.monitoring.yaml file will now 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;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prom_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;grafana-storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Spin up Services
&lt;/h2&gt;

&lt;p&gt;Now that we have written all our configurations we can go ahead and start up all our monitoring services.&lt;/p&gt;

&lt;p&gt;Since we want to run both our compose files at the same time we will add an include element to our first (application) compose file.&lt;/p&gt;

&lt;p&gt;Open the &lt;code&gt;compose.yaml&lt;/code&gt; file and add the below block of code to the very top of the file.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;&lt;code&gt;compose.yaml&lt;/code&gt;&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;compose.monitoring.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now if you have any of the earlier containers running already, you should stop them and start everything all over again. Use the command below:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose down &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;All our containers should be up and running and available now so we can go ahead and create our grafana dashboards now.&lt;/p&gt;

&lt;p&gt;If you head to your prometheus web UI (https:///prometheus) and click on &lt;code&gt;target health&lt;/code&gt; under the &lt;code&gt;status&lt;/code&gt; dropdown you should see the different scrape targets we configured in our prometheus configuration.&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Prometheus Web UI&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04rtlca4bh0x4xmhvlvq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04rtlca4bh0x4xmhvlvq.png" alt="Prometheus-UI" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  Create Dashboards
&lt;/h1&gt;

&lt;p&gt;To begin creating our Grafana dashboards we need to login to our Grafana web UI. Navigate to https:///grafana, the login page will popup and you need to enter the default username and password which are both &lt;code&gt;admin&lt;/code&gt;. &lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Grafana Web UI Login&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz9ft7dp20pb05f9x7yrx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz9ft7dp20pb05f9x7yrx.png" alt="grafana-login" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you are logged in you will be asked to change your password, do that and let's continue.&lt;/p&gt;


&lt;h2&gt;
  
  
  Add Data sources
&lt;/h2&gt;

&lt;p&gt;Upon logging in you will see a welcome page that looks like the one below, click on the &lt;code&gt;Data Sources&lt;/code&gt; box (illustrated by the arrow) to add your first data source.&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Grafana welcome page&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F67w47ufallrdbtl7u2pt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F67w47ufallrdbtl7u2pt.png" alt="grafana-add-data-source" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Prometheus Data source
&lt;/h3&gt;

&lt;p&gt;✨ Click on &lt;code&gt;Prometheus&lt;/code&gt; from the options on the page that opens up, it should be right on top.&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Prometheus data source&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqigt6sluich3l3vnxmrp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqigt6sluich3l3vnxmrp.png" alt="prometheus-data-source" width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✨ On the next page, scroll down and enter &lt;code&gt;https://&amp;lt;yourdomain&amp;gt;/prometheus&lt;/code&gt; as your &lt;code&gt;prometheus server url&lt;/code&gt; under the &lt;code&gt;Connection&lt;/code&gt; category.&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Prometheus server url&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp37kf6k2jfq2gxxvgj4t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp37kf6k2jfq2gxxvgj4t.png" alt="prom-server-url" width="800" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✨ Scroll to the bottom of the page and click on &lt;code&gt;Save and test&lt;/code&gt;, you should get a confirmation that the prometheus API has been successfully queried, as you can see below&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Successfully queried Prometheus API&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq50ik61hw2k3hek60d31.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq50ik61hw2k3hek60d31.png" alt="prom-api" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Loki Data Source
&lt;/h3&gt;

&lt;p&gt;✨ To add Loki as a data source, click on the hamburger button on the top left part of the page and under connections, click on data sources as shown below&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Data sources menu&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzlpixeglc47xiy6bd0x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzlpixeglc47xiy6bd0x.png" alt="data-source" width="800" height="664"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✨ On the next page, click on the &lt;code&gt;add new data source&lt;/code&gt; button on the top right corner of the page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2kgu2orcada96d3g2om.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2kgu2orcada96d3g2om.png" alt="add-new-data-src" width="800" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✨ Scroll down a bit and select &lt;code&gt;Loki&lt;/code&gt; as the data source. Just as we did with prometheus, in the next page enter your loki url in the connection category, finally scroll down and &lt;code&gt;save and test&lt;/code&gt;.&lt;/p&gt;


&lt;h5&gt;
  
  
  &lt;strong&gt;Loki data source addition&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzctxc5j04g9ctqb55kfs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzctxc5j04g9ctqb55kfs.png" alt="loki-data-source" width="550" height="621"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Import cAdvisor Dashboard
&lt;/h2&gt;

&lt;p&gt;Now that we have added our Data sources we can proceed with creating our dashboard. We won't be creating these dashboards from scratch though, we will simply import dashboards that have been created by community members that fit what we are trying to do.&lt;/p&gt;

&lt;p&gt;You can find these dashboards at &lt;code&gt;https://grafana.com/grafana/dashboards/&lt;/code&gt; but I've done the heavy lifting for you and found the dashboard we will use, however feel free to explore the link and see if any other dashboards fit your need better.&lt;/p&gt;

&lt;p&gt;✨ Navigate to Grafana web Ui homepage and click on the &lt;code&gt;Dashboards&lt;/code&gt; box&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Create first Dashboard&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tf5hyat7h280df8j50e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tf5hyat7h280df8j50e.png" alt="add-dash" width="800" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✨ On the resultant new page click on &lt;code&gt;import dashboard&lt;/code&gt; and in the next page enter the code &lt;code&gt;19792&lt;/code&gt; and click on &lt;code&gt;load&lt;/code&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Import dashboard - cAdvisor&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhh51wsovovkvfv9dvepf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhh51wsovovkvfv9dvepf.png" alt="load-dash" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✨ On the new page, scroll to the bottom, in the Prometheus category, click and select your prometheus data source from the drop down and click on &lt;code&gt;import&lt;/code&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;Add cAdvisor Dashboard&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk2cjqn0ocf0iis6615an.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk2cjqn0ocf0iis6615an.png" alt="import-dash" width="800" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✨ You should be able to see your beautiful dashboard now, it will look like what I have below&lt;/p&gt;
&lt;h4&gt;
  
  
  cAdvisor Dashboard
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frimrafj5ki10vl9et7z6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frimrafj5ki10vl9et7z6.png" alt="cadvisor-dash1" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0rx7i4hq1hx7ioovwzq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0rx7i4hq1hx7ioovwzq.png" alt="cadvisor-dash2" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1wyxi6kz7utlksy5g6m5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1wyxi6kz7utlksy5g6m5.png" alt="cadvisor-dash3" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Import Loki Dashboard
&lt;/h2&gt;

&lt;p&gt;✨ To add a new dashboard, click on the hamburger button on the top left and click on &lt;code&gt;dashboards&lt;/code&gt; from the options.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fti2u0g153cmjp184nlm9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fti2u0g153cmjp184nlm9.png" alt="add-new-dash" width="590" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✨ When the next page loads, click on the arrow on the &lt;code&gt;New&lt;/code&gt; button for the dropdown and select &lt;code&gt;import&lt;/code&gt; from the options.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgegljs4xzur4o3qp0ihx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgegljs4xzur4o3qp0ihx.png" alt="new-dash" width="800" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✨ From here you know the drill, enter the code &lt;code&gt;13186&lt;/code&gt;, load the dashboard, add your loki data source and import the dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5e4z9keszihcq34yfgap.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5e4z9keszihcq34yfgap.png" alt="loki-dash-code" width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzoyswn7jy9aj9qb5q9tn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzoyswn7jy9aj9qb5q9tn.png" alt="Import-loki-dash" width="800" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✨ You should see the dashboard now, tinker around with it to see more information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq35n1w6ibt9e7zp2p87c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq35n1w6ibt9e7zp2p87c.png" alt="loki-dash" width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✨ You could also explore your Loki data source and run custom queries as you see fit.&lt;/p&gt;


&lt;h2&gt;
  
  
  Import Traefik Dashboard
&lt;/h2&gt;

&lt;p&gt;The process to import the traefik dashboard is the same as we went through for the Loki dashboard.&lt;/p&gt;

&lt;p&gt;✨ Enter the code &lt;code&gt;4475&lt;/code&gt; and load the dashboard, add your prometheus data source and import the dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy843jza21n38bgdq5jjl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy843jza21n38bgdq5jjl.png" alt="Import-traefik-dash" width="800" height="628"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✨ Now you should have a dashboard that looks like the one below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Famx0cyheu7sxhxzd23vi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Famx0cyheu7sxhxzd23vi.png" alt="traefik-dash" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Congratulations! After all the effort and time invested, we’ve successfully Dockerized our full-stack application with a FastAPI backend and a React frontend, set up a reverse proxy, and deployed a robust monitoring and observability stack using Prometheus, Grafana, Loki, Promtail, and cAdvisor. Along the way, we built fully functional dashboards to keep an eye on system performance and logs.&lt;/p&gt;

&lt;p&gt;This journey most definitely demanded patience and determination, but it also provided the opportunity to sharpen essential skills in containerization, deployment, and observability. From orchestrating containers to visualizing metrics and logs, these are valuable tools in any developer's toolkit.&lt;/p&gt;

&lt;p&gt;Take a moment to appreciate how far you’ve come, whether it’s mastering Docker, setting up monitoring systems, or troubleshooting with confidence, this project is a testament to your growth and perseverance. Here's to many more successful deployments ahead!&lt;/p&gt;

&lt;p&gt;If you found this post helpful in anyway, please let me know in the comments and share with your friends. If you have a better way of implementing any of the steps I took here, please do let me know, I love to learn new (~better~) ways of doing things.&lt;/p&gt;

&lt;p&gt;Follow for more DevOps content broken down into simple understandable structure.&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__887485"&gt;
    &lt;a href="/chigozieco" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F887485%2Fd0a1d1a7-f8d7-47e9-8940-b97ae21f54c0.jpg" alt="chigozieco image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/chigozieco"&gt;ChigozieCO&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/chigozieco"&gt;Cloud and DevOps Engineer who's a little bit obsessed with the whole cloud computing scene, always ready to dive into the nitty-gritty of cloud infrastructure, DevOps and Security.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;If this post has helped you, consider supporting me: &lt;a href="https://buymeacoffee.com/chigozieco" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.buymeacoffee.com%2Fassets%2Fimg%2Fcustom_images%2Fyellow_img.png" alt="Buy Me A Coffee" width="170" height="37"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>monitoring</category>
      <category>proxy</category>
      <category>prometheus</category>
    </item>
    <item>
      <title>Terraform Remote Backend: How to Manage Terraform State File for Easier Collaboration across Teams</title>
      <dc:creator>ChigozieCO</dc:creator>
      <pubDate>Wed, 06 Nov 2024 11:05:41 +0000</pubDate>
      <link>https://forem.com/chigozieco/terraform-remote-backend-how-to-manage-terraform-state-file-for-easier-collaboration-across-teams-3dip</link>
      <guid>https://forem.com/chigozieco/terraform-remote-backend-how-to-manage-terraform-state-file-for-easier-collaboration-across-teams-3dip</guid>
      <description>&lt;p&gt;Have you ever wondered how Terraform manages to keep track of the resources you create with terraform and apply your changes to just those specific resources whenever you try to create and update resources? &lt;/p&gt;

&lt;p&gt;If you have used terraform you would have noticed that every time you ran &lt;code&gt;terraform plan&lt;/code&gt;, &lt;code&gt;terraform apply&lt;/code&gt; or &lt;code&gt;terraform destroy&lt;/code&gt; Terraform was able to find the resources it created previously and update them accordingly. Howwww? How did Terraform know which resources it was supposed to manage? Your AWS account could have several different infrastructures deployed through a variety of ways (eg some manually, some via Terraform, some via the CLI, some via AWS SAM etc), so how does Terraform know the particular infrastructure it created and hence is responsible for?&lt;/p&gt;

&lt;p&gt;If you find this post useful, consider supporting me: &lt;a href="https://buymeacoffee.com/chigozieco" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.buymeacoffee.com%2Fassets%2Fimg%2Fcustom_images%2Fyellow_img.png" alt="Buy Me A Coffee" width="170" height="37"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh9vbzv36bssh9wp4b370.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh9vbzv36bssh9wp4b370.gif" alt="Howww" width="300" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In two words “Terraform State”&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📌 What is Terraform State
&lt;/h3&gt;

&lt;p&gt;Terraform maintains track of everything it creates in your cloud environments so that it will know what it created and can go back and make modifications for you if you need to update or remove something later. This is what is meant when they say that Terraform is a stateful application. &lt;/p&gt;

&lt;p&gt;Terraform records information about what infrastructure it created in a Terraform state file. By default, when you run &lt;code&gt;Terraform init&lt;/code&gt; in the folder &lt;strong&gt;"/hello/world"&lt;/strong&gt;, it will create the file &lt;code&gt;/hello/world/terraform.tfstate&lt;/code&gt;. This file includes a customized JSON format that records a mapping between the Terraform resources in your configuration files and their actual representations. &lt;/p&gt;

&lt;p&gt;Every time you run Terraform, it can fetch the latest status of your resources from your cloud account and compare that to what’s in your Terraform configurations to determine what changes need to be applied. In other words, the output of the plan command is a diff between the code on your computer and the infrastructure deployed in the real world, as discovered via IDs in the state file.&lt;/p&gt;

&lt;h4&gt;
  
  
  ⚠️⚠️ Note
&lt;/h4&gt;

&lt;p&gt;The state file format is a private API that is meant only for internal use within Terraform. You should never edit the Terraform state files by hand or write code that reads them directly. If for some reason you need to manipulate the state file—which should be a relatively rare occurrence—use the terraform import or terraform state commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  📌 Benefits of the Terraform State File
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✨ &lt;strong&gt;Idempotence&lt;/strong&gt;&lt;br&gt;
Whenever a Terraform configuration is applied, Terraform checks if there is an actual change made. Only the resources that are changed will be updated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✨ &lt;strong&gt;Deducing dependencies&lt;/strong&gt;&lt;br&gt;
Terraform maintains a list of dependencies in the state file so that it can properly deal with dependencies that no longer exist in the current configuration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✨ &lt;strong&gt;Performance&lt;/strong&gt;&lt;br&gt;
Terraform can be told to skip the refresh even when a configuration change is made. Only a particular resource can be refreshed without triggering a full refresh of the state, hence improving performance.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✨ &lt;strong&gt;Collaboration&lt;/strong&gt;&lt;br&gt;
State keeps track of the version of an applied configuration, and it's stored in a remote, shared location. So collaboration is easily done without overwriting. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✨ &lt;strong&gt;Auditing&lt;/strong&gt;&lt;br&gt;
Invalid access can be identified by enabling logging.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✨ &lt;strong&gt;Safer storage&lt;/strong&gt;&lt;br&gt;
Storing state on the remote server helps prevent sensitive information. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📌 Limitations Teams Face with Terraform State Files
&lt;/h3&gt;

&lt;p&gt;The above is all great and nice if you are working on your project alone, if you’re using Terraform for a personal project, you can get away with storing state in a single &lt;code&gt;terraform.tfstate&lt;/code&gt; file that lives locally on your computer. This will work just fine without any form of conflicts occurring. However, when working on a large project along side your team members, using Terraform, storing the &lt;code&gt;terraform.tfstate&lt;/code&gt; file will present the following problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✨ &lt;strong&gt;Shared storage for state files&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To be able to use Terraform to update your infrastructure, each of your team members needs access to the same Terraform state files. That means you need to store those files in a shared location. If you're thinking of using version control such as Git, think again. Although you should definitely store your Terraform code in version control, storing Terraform state in version control is a bad idea as it would lead to a new set of problems like manual error, locking and secrets exposure. &lt;/p&gt;

&lt;p&gt;Manual error refers to the ease which which one can forget to pull down the latest changes from version control before running Terraform or to push your latest changes to version control after running Terraform. It’s just a matter of time before someone on your team runs Terraform with out-of-date state files and as a result, accidentally rolls back or duplicates previous deployments. The problem of secrets exposure presents itself due to the fact that all data in Terraform state files is stored in plain text. This is a problem because certain Terraform resources need to store sensitive data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✨ &lt;strong&gt;Locking state files&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As soon as data is shared, you run into a new problem: locking. Without locking, if two team members are running Terraform at the same time, you can run into race conditions as multiple Terraform processes make concurrent updates to the state files, leading to conflicts, data loss, and state file corruption. Locking using version control such as Git is not possible as most version control systems do not provide any form of locking that would prevent two team members from running terraform apply on the same state file at the same time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✨ &lt;strong&gt;Isolating state files&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When making changes to your infrastructure, it’s a best practice to isolate different environments. For example, when making a change in a testing or staging environment, you want to be sure that there is no way you can accidentally break production. But how can you isolate your changes if all of your infrastructure is defined in the same Terraform state file? &lt;/p&gt;

&lt;h3&gt;
  
  
  📌 Using Terraform's Remote Backend.
&lt;/h3&gt;

&lt;p&gt;The ideal method to handle shared storage for state files is to use Terraform's built-in support for remote backends rather than version control. How Terraform loads and saves state is determined by a backend. The local backend, which saves the state file on your local drive, is the default backend you use when you work on a project alone. You can keep the state file in a distant, shared store using remote backends. There are several remote backends available, including:&lt;/p&gt;

&lt;p&gt;✨ HashiCorp's Terraform Cloud and &lt;br&gt;
✨ Terraform Enterprise, &lt;br&gt;
✨ Amazon S3, &lt;br&gt;
✨ Azure Storage, &lt;br&gt;
✨ Google Cloud Storage and others.&lt;/p&gt;

&lt;p&gt;Remote backends solve the manual error, locking and secrets exposure errors we mentioned that could arise with the use of version control.&lt;/p&gt;

&lt;p&gt;There is no danger of manual human mistake since, after configuring a remote backend, as each time you run plan or apply Terraform will automatically load the state file from that backend and will automatically put the state file in that backend after each apply.&lt;/p&gt;

&lt;p&gt;In regards to the error posed as a result of locking you should note that locking is supported natively by the majority of remote backends. Terraform will automatically get a lock in order to execute &lt;code&gt;terraform apply&lt;/code&gt;; if someone else is currently running apply, they will already have the lock, and you will need to wait. Apply can be performed with the &lt;code&gt;-locktimeout= &amp;lt;TIME&amp;gt;&lt;/code&gt; argument to tell Terraform to wait for a lock to be released up to TIME (for example, &lt;code&gt;-lock-timeout=10m&lt;/code&gt; will wait for 10 minutes).&lt;/p&gt;

&lt;p&gt;Most of the remote backends natively support encryption in transit and encryption at rest of the state file. Although the best solution would be for Terraform to natively support encrypting secrets within the state file, these remote backends reduce most of the security concerns, given that at least the state file isn’t stored in plain text on disk anywhere.&lt;/p&gt;

&lt;p&gt;We would be using Amazon S3, AWS' managed file store. This is the best bet as a remote backend when working within an AWS environment for several reasons.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;NOTE&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The easiest way, and might I say most effective way, and also quickest way to do this will be to manually create your S3 bucket and DynamoDB table via the management console or via the AWS CLI and then configure your backend in your terraform script. However that method introduces a level of manual configuration that could introduce manual error. &lt;/p&gt;

&lt;p&gt;Even though the method I am about to show you makes the whole process automated and repeatable, it's limitation is that the creation of our resources must then be in a two step process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First we write Terraform code to create the S3 bucket and DynamoDB table and deploy that code with a local backend.&lt;/li&gt;
&lt;li&gt;Then we go back to the Terraform code, add a remote backend configuration to it to use the newly created S3 bucket and DynamoDB table, and run terraform init to copy your local state to S3.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you ever want to delete the S3 bucket and DynamoDB table, you’d have to do this two-step process in reverse:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First go to the Terraform code, remove the backend configuration, and rerun terraform init to copy the Terraform state back to your local disk.&lt;/li&gt;
&lt;li&gt;Then run terraform destroy to delete the S3 bucket and DynamoDB
table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✨ I personally always prefer to create my bucket and table manually. &lt;br&gt;
Also you can use the same bucket and table for several different configurations but be careful when you do this so that you don't delete these resources and lose the state files of so many of your environments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Follow along to see how you can use terraform to create the bucket and table where you want to store your Terraform state. &lt;/p&gt;
&lt;h3&gt;
  
  
  📌 Creating an S3 Remote Backend
&lt;/h3&gt;

&lt;p&gt;To enable remote state storage with Amazon S3, the first step is to create an S3 bucket. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✨ &lt;strong&gt;Create a &lt;code&gt;main.tf&lt;/code&gt; file in a new folder.&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use any code editor of your choice, eg vscode, Specify AWS as the provider at the top of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;✨ Next, create an S3 bucket by using the aws_s3_bucket resource:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"tf-state"&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;"tf-code-state"&lt;/span&gt;

  &lt;span class="c1"&gt;#The below line of code will prevent accidental deletion of this bucket&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;prevent_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;⚠️⚠️ Note that S3 bucket names must be globally unique among all AWS customers. Therefore, you will need to change the bucket parameter from "tf-code-state" (which I already created) to your own name. Make sure to remember this name and take note of what AWS region you’re using because you’ll need both pieces of information again a little later on.&lt;/p&gt;
&lt;h3&gt;
  
  
  📌 Adding Security Elements to our Remote Backend
&lt;/h3&gt;

&lt;p&gt;Let’s now add several extra layers of protection to this S3 bucket.&lt;/p&gt;

&lt;p&gt;✨ First, use the &lt;code&gt;aws_s3_bucket_versioning&lt;/code&gt; resource to enable&lt;br&gt;
versioning on the S3 bucket so that every update to a file in the bucket actually creates a new version of that file. This allows you to see older versions of the file and revert to those older versions at any time, which can be a useful fallback mechanism if something goes wrong:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Next set of lines of code will enable versioning so that we can see previous versions of our state file&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_versioning"&lt;/span&gt; &lt;span class="s2"&gt;"enabled"&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;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tf-state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;versioning_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;✨ Next we will use the &lt;code&gt;aws_s3_bucket_server_side_encryption_configuration&lt;/code&gt;&lt;br&gt;
resource to turn server-side encryption on by default for all data written to this S3 bucket. This ensures that your state files, and any secrets they might contain, are always encrypted on disk when stored in S3:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Now we enable server side encryption by default&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_server_side_encryption_configuration"&lt;/span&gt; &lt;span class="s2"&gt;"dafault"&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;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tf-state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;apply_server_side_encryption_by_default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;sse_algorithm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AES256"&lt;/span&gt;
    &lt;span class="p"&gt;}&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;✨ The very next thing we have to do is to block all public access to the s3 bucket using the &lt;code&gt;aws_s3_bucket_public_access_block&lt;/code&gt; resource. S3 buckets are private by default, but as they are often used to serve static content—e.g., images, fonts, CSS, JS, HTML—it is possible, even easy, to make the buckets public. Since your Terraform state files may contain sensitive data and secrets, it’s worth&lt;br&gt;
adding this extra layer of protection to ensure no one on your team can ever accidentally make this S3 bucket public:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Explicitly block all public access to the s3 bucket&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_public_access_block"&lt;/span&gt; &lt;span class="s2"&gt;"public_access"&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;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tf-state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;block_public_acls&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;block_public_policy&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;ignore_public_acls&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;restrict_public_buckets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  📌 Create a DynamoDB Table for Locking
&lt;/h3&gt;

&lt;p&gt;✨ The next thing we have to do is to create a DynamoDB table that we would use for locking. DynamoDB is Amazon’s distributed key–value store. It supports strongly consistent&lt;br&gt;
reads and conditional writes, which are all the ingredients you need for a distributed lock system. Moreover, it’s completely managed, so you don’t have any infrastructure to run yourself, and it’s inexpensive, with most Terraform usage easily fitting into the free tier.&lt;/p&gt;

&lt;p&gt;To use DynamoDB for locking with Terraform, you must create a&lt;br&gt;
DynamoDB table that has a primary key called &lt;strong&gt;LockID&lt;/strong&gt; (with this exact spelling and capitalization). You can create such a table using the &lt;code&gt;aws_dynamodb_table&lt;/code&gt; resource:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create dynamodb table for terraform state locking&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_dynamodb_table"&lt;/span&gt; &lt;span class="s2"&gt;"tf_state_lock"&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="nx"&gt;tf-state-lock&lt;/span&gt;
  &lt;span class="nx"&gt;billing_mode&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PAY_PER_REQUEST"&lt;/span&gt;
  &lt;span class="nx"&gt;hash_key&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"LockID"&lt;/span&gt;

  &lt;span class="nx"&gt;attribute&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;"LockID"&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"S"&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;h3&gt;
  
  
  📌 Create Resources
&lt;/h3&gt;

&lt;p&gt;Now we will create the table and the bucket.&lt;/p&gt;

&lt;p&gt;We have configured our remote backend yet (remember the two step process we spoke about earlier?) so our statefile will be stored locally for now.&lt;/p&gt;

&lt;p&gt;Run the commands below to initialize terraform, show the plan and create the resources:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
terraform plan
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;⚠️ &lt;/p&gt;

&lt;p&gt;Note that when you run &lt;code&gt;terraform apply&lt;/code&gt; without the &lt;code&gt;-auto-approve&lt;/code&gt; flag you will to prompted to either apply or decline the creation with a &lt;code&gt;yes&lt;/code&gt; or &lt;code&gt;no&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Type &lt;code&gt;yes&lt;/code&gt; to continue with the process.&lt;/p&gt;
&lt;h3&gt;
  
  
  📌 Configure the remote backend and move the state file
&lt;/h3&gt;

&lt;p&gt;Now our bucket and DynamoDB table are both created, but we still have our statefile save locally, therefore we need to move it to these resources we just created.&lt;/p&gt;

&lt;p&gt;To do this, first we will update our configuration, we need to add a backend configuration to our Terraform code. This configuration will be in the &lt;code&gt;terraform&lt;/code&gt; block as shown below.&lt;/p&gt;

&lt;p&gt;At the top of your &lt;code&gt;main.tf&lt;/code&gt; add the below block of code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Replace this with your bucket name!&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;"terraform-up-and-running-state"&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"global/s3/&amp;lt;your s3 bucket name&amp;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-2"&lt;/span&gt;

    &lt;span class="c1"&gt;# Replace this with your DynamoDB table name!&lt;/span&gt;
    &lt;span class="nx"&gt;dynamodb_table&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;your dynamodb table name&amp;gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;encrypt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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 you simply have to rerun the &lt;code&gt;terraform init&lt;/code&gt; command again.&lt;/p&gt;

&lt;p&gt;This will instruct Terraform to store your state file in this S3 bucket. When you run this command Terraform will automatically detect that you already have a state file locally and prompt you to copy it to the new S3 backend.&lt;/p&gt;

&lt;p&gt;After running the command, your Terraform state will be stored in the S3 bucket. You can confirm that your state file is in your bucket by heading to the AWS Management Console, clicking on S3 bucket and clicking into your bucket.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cleanup
&lt;/h2&gt;

&lt;p&gt;To destroy your infrastructure and cleanup your environment you need to take out the backend configuration you just added, run &lt;code&gt;terraform init&lt;/code&gt; again and then run &lt;code&gt;terraform destroy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is so that the state files will be copied back into your local environment and you can delete the S3 buckets. This is only necessary because your configuration is the same configuration you used in creating the S3 bucket and Dynamodb table. Supposing you didn't create the backend S3 bucket and dynamodb table with this configuration you wouldn't need to remove the backend configuration from your setup.&lt;/p&gt;

&lt;p&gt;Hope you found this helpful, if you did please share with your community and connect with me on &lt;a href="https://www.linkedin.com/in/chigozienm" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and follow me on &lt;a href="https://github.com/ChigozieCO" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Follow for more DevOps content broken down into simple understandable structure.&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__887485"&gt;
    &lt;a href="/chigozieco" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F887485%2Fd0a1d1a7-f8d7-47e9-8940-b97ae21f54c0.jpg" alt="chigozieco image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/chigozieco"&gt;ChigozieCO&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/chigozieco"&gt;Cloud and DevOps Engineer who's a little bit obsessed with the whole cloud computing scene, always ready to dive into the nitty-gritty of cloud infrastructure, DevOps and Security.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>career</category>
      <category>discuss</category>
      <category>productivity</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Complete Guide to Automate the Deployment of the Sock Shop Application on Kubernetes with IaC, CI/CD, and Monitoring</title>
      <dc:creator>ChigozieCO</dc:creator>
      <pubDate>Tue, 22 Oct 2024 12:13:38 +0000</pubDate>
      <link>https://forem.com/chigozieco/complete-guide-to-automate-the-deployment-of-the-sock-shop-application-on-kubernetes-with-iac-cicd-and-monitoring-5hlm</link>
      <guid>https://forem.com/chigozieco/complete-guide-to-automate-the-deployment-of-the-sock-shop-application-on-kubernetes-with-iac-cicd-and-monitoring-5hlm</guid>
      <description>&lt;p&gt;This project is about deploying a microservices-based application using automated tools to ensure quick, reliable, and secure deployment on Kubernetes. By focusing on Infrastructure as Code, you'll create a reproducible and maintainable deployment process that leverages modern DevOps practices and tools.&lt;/p&gt;

&lt;p&gt;For a detailed breakdown of what this project is trying to achieve check out &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/blob/main/Requirements.md" rel="noopener noreferrer"&gt;the requirements here&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;An AWS Account&lt;/li&gt;
&lt;li&gt;AWS CLI installed and configured&lt;/li&gt;
&lt;li&gt;Terraform installed&lt;/li&gt;
&lt;li&gt;Kubectl installed&lt;/li&gt;
&lt;li&gt;Helm&lt;/li&gt;
&lt;li&gt;A custom domain&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Set Up AWS Hosted Zone and Custom Domain
&lt;/h1&gt;

&lt;p&gt;The first thing to do to begin this project is to create a hosted zone and configure our custom domain.&lt;/p&gt;

&lt;p&gt;I have already purchased a custom domain &lt;a href="http://projectchigozie.me/" rel="noopener noreferrer"&gt;projectchigozie.me&lt;/a&gt; and so I created an AWS hosted zone to host this domain. I didn't use terraform to create this hosted zone because this step still required manually configuration to add the nameservers to the domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steps to create an AWS hosted zone
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Navigate the the &lt;a href="https://aws.amazon.com/" rel="noopener noreferrer"&gt;aws management console&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click on services in the top left corner, click on the &lt;code&gt;networking and content delivery&lt;/code&gt; category and choose the &lt;code&gt;Route53&lt;/code&gt; subcategory.&lt;/li&gt;
&lt;li&gt;Click on &lt;code&gt;create hosted zone&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;When it opens up, enter your custom domain name.&lt;/li&gt;
&lt;li&gt;Leave the rest as default, you can add a tag if you want to.&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;create hosted zone&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c1f7eqgkpdnzxmh3syl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c1f7eqgkpdnzxmh3syl.png" alt="Hosted-zone-console" width="800" height="795"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the hosted zone is created, we then retrieve the namespaces from the created hosted zone and use it to replace those already in our custom domain.&lt;/p&gt;

&lt;p&gt;The specific steps to take to do this will vary depending on your domain name registrar but it's pretty much very easy across board.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F4427021c-660a-4bf3-b219-febe886a3174" class="article-body-image-wrapper"&gt;&lt;img alt="hosted-zone" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F4427021c-660a-4bf3-b219-febe886a3174" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Provision AWS EKS Cluster with Terraform
&lt;/h1&gt;

&lt;p&gt;For automation and to speed up the process, we will write a terraform script to deploy an EKS cluster and configure the necessary VPC, subnets, security groups and IAM roles.&lt;/p&gt;

&lt;p&gt;We won't be reinventing the wheel here as there are a lot of terraform modules out there that do just exactly what we are trying to do. I will be using the official terraform/aws vpc and eks modules.&lt;/p&gt;

&lt;p&gt;My terraform script for the EKS cluster provisioning can be found in the &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/tree/main/terraform" rel="noopener noreferrer"&gt;terraform directory&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a &lt;code&gt;.gitignore&lt;/code&gt; File
&lt;/h3&gt;

&lt;p&gt;Before we begin it is usually best to create a &lt;code&gt;.gitignore&lt;/code&gt; where we will specify the files that will not be pushed to version control.&lt;/p&gt;

&lt;p&gt;For the sake of keeping this post short, find my &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/blob/main/.gitignore" rel="noopener noreferrer"&gt;git ignore file here&lt;/a&gt;, copy its contents and add to your own .gitignore file.&lt;/p&gt;




&lt;h3&gt;
  
  
  Setup Remote Backend
&lt;/h3&gt;

&lt;p&gt;We will make use of remote backend for this project, using an S3 bucket to save our statefiles abd DynamoDb to save our lock file.&lt;/p&gt;

&lt;p&gt;Create an S3 bucket and name it anything you like, remember that your bucket name must be unique so find a unique name to use, you can enable versioning in your bucket to ensure older statefiles are not deleted incase you need to go go back to a previous configuration.&lt;/p&gt;

&lt;p&gt;In that bucket create 2 folders, I named mine &lt;code&gt;eks&lt;/code&gt; and &lt;code&gt;k8s&lt;/code&gt;, for the two different terraform scripts.&lt;/p&gt;

&lt;p&gt;Next create a DynamoDb table with any name of your choosing, ensure the partition key is named &lt;code&gt;LockID&lt;/code&gt; written exactly like this and leave all other settings as default. We will use them for our terraform configurations.&lt;/p&gt;




&lt;h3&gt;
  
  
  Add Providers
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;terraform/main.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;First create a new directory called &lt;code&gt;terraform&lt;/code&gt; this is where all our Terraform scripts will be.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;main.tf&lt;/code&gt; file in the &lt;code&gt;terraform&lt;/code&gt; directory and add the code below to the file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Copyright (c) HashiCorp, Inc.&lt;/span&gt;

&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&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;"hashicorp/aws"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&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;"sockshop-statefiles"&lt;/span&gt; &lt;span class="c1"&gt;# Replace with your bucket name&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eks/terraform.tfstate"&lt;/span&gt; &lt;span class="c1"&gt;# Replace with your first folder name&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="nx"&gt;dynamodb_table&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sock-shop-lockfile"&lt;/span&gt; &lt;span class="c1"&gt;# Replace with your DynamoDb table name&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
  &lt;span class="nx"&gt;shared_credentials_files&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"~/.aws/credentials"&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;h3&gt;
  
  
  Retrieve Data from AWS
&lt;/h3&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;terraform/data.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;We will retrieve the availability zones data from AWS so we can use them in creating our resources and so create a &lt;code&gt;data.tf&lt;/code&gt; file in the terraform directory and add the below lines of code to the file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Filter out local zones, which are not currently supported &lt;/span&gt;
&lt;span class="c1"&gt;# with managed node groups&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_availability_zones"&lt;/span&gt; &lt;span class="s2"&gt;"available"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filter&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;"opt-in-status"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"opt-in-not-required"&lt;/span&gt;&lt;span class="p"&gt;]&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;h3&gt;
  
  
  Create VPC and Other Networking Resources
&lt;/h3&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;terraform/vpc.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Also in the terraform directory create a new file &lt;code&gt;vpc.tf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can find the official terraform/aws vpc module &lt;a href="https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest" rel="noopener noreferrer"&gt;here&lt;/a&gt;, the code below is mostly the same with a little modification.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create vpc using the terraform aws vpc module&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&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;"terraform-aws-modules/vpc/aws"&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpcname&lt;/span&gt;
  &lt;span class="nx"&gt;cidr&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;

  &lt;span class="nx"&gt;azs&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_availability_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;private_subnets&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"10.0.1.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"10.0.2.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"10.0.3.0/24"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;public_subnets&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"10.0.101.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"10.0.102.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"10.0.103.0/24"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;enable_nat_gateway&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;single_nat_gateway&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;enable_dns_hostnames&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;public_subnet_tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"kubernetes.io/role/internal-elb"&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# If you want to deploy load balancers to a subnet, the subnet must have &lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="nx"&gt;private_subnet_tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"kubernetes.io/role/internal-elb"&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# If you want to deploy load balancers to a subnet, the subnet must have &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;h3&gt;
  
  
  Define VPC Variables
&lt;/h3&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;terraform/variables.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Create a &lt;code&gt;variables.tf&lt;/code&gt; file in the terraform directory, we will define our variables in this file. If you noticed, we have already referenced some variables already in the code above and your code should already be throwing errors. To rectify them, create the file and add the below code to it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"region"&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;"The region where the VPC will be located"&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;string&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;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"vpcname"&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;"Vpc name"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create EKS Cluster
&lt;/h3&gt;

&lt;p&gt;We will also be using the official terraform EKS module to create our EKS cluster. You can find the &lt;a href="https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/latest" rel="noopener noreferrer"&gt;EKS Module here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create a new file in the terraform directory, name it &lt;code&gt;eks.tf&lt;/code&gt;, this is where we will store our terraform script to create an eks cluster.&lt;/p&gt;

&lt;p&gt;The reason we are breaking down our code into several files is for readability and maintainability, it makes the code easier to read and maintain when all scripts that fall into the same group are found in the same place.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;terraform/eks.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Add the below code to the eks.tf file&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"eks"&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;"terraform-aws-modules/eks/aws"&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt;                                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 20.0"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_name&lt;/span&gt;                             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_version&lt;/span&gt;                          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.30"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_endpoint_public_access&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;vpc_id&lt;/span&gt;                                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_id&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_ids&lt;/span&gt;                               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_subnets&lt;/span&gt;

  &lt;span class="c1"&gt;# EKS Managed Node Group(s)&lt;/span&gt;
  &lt;span class="nx"&gt;eks_managed_node_group_defaults&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Starting on 1.30, AL2023 is the default AMI type for EKS managed node groups&lt;/span&gt;
    &lt;span class="nx"&gt;ami_type&lt;/span&gt;                               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AL2023_x86_64_STANDARD"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;eks_managed_node_groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;one&lt;/span&gt; &lt;span class="p"&gt;=&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;"node-group-1"&lt;/span&gt;
      &lt;span class="nx"&gt;instance_types&lt;/span&gt;                       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"t2.medium"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

      &lt;span class="nx"&gt;min_size&lt;/span&gt;                             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="nx"&gt;max_size&lt;/span&gt;                             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
      &lt;span class="nx"&gt;desired_size&lt;/span&gt;                         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;two&lt;/span&gt; &lt;span class="p"&gt;=&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;"node-group-2"&lt;/span&gt;
      &lt;span class="nx"&gt;instance_types&lt;/span&gt;                       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"t2.medium"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

      &lt;span class="nx"&gt;min_size&lt;/span&gt;                             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="nx"&gt;max_size&lt;/span&gt;                             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
      &lt;span class="nx"&gt;desired_size&lt;/span&gt;                         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Cluster access entry&lt;/span&gt;
  &lt;span class="c1"&gt;# To add the current caller identity as an administrator&lt;/span&gt;
  &lt;span class="nx"&gt;enable_cluster_creator_admin_permissions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Looking at the above code, you will notice that we referenced a variable and so we need to declare that variable in our &lt;code&gt;variables.tf&lt;/code&gt; file, and so we will do that next.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;terraform/variables.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Add this to your variables.tf file&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"cluster_name"&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;"Name of EKS cluster"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Declare Outputs
&lt;/h3&gt;

&lt;p&gt;We will need some details from our configuration after the provisioning of our resources is over to enable us deploy our app and so we will tell terraform to give us those details when it is done.&lt;/p&gt;

&lt;p&gt;We do this by defining our outputs in an &lt;code&gt;output.tf&lt;/code&gt; file. Create another file in the terraform directory, name it &lt;code&gt;outputs.tf&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;terraform/outputs.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Add the below code to your outputs.tf file&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cluster_endpoint"&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;"Endpoint for EKS control plane"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_endpoint&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cluster_security_group_id"&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;"Security group ids attached to the cluster control plane"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_security_group_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"region"&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;"AWS region"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&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="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cluster_name"&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;"Kubernetes Cluster Name"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cluster_oidc_issuer_url"&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;"The URL on the EKS cluster for the OpenID Connect identity provider"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_oidc_issuer_url&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"aws_account_id"&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;"Account Id of your AWS account"&lt;/span&gt;
  &lt;span class="nx"&gt;sensitive&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;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_caller_identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cluster_certificate_authority_data"&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;"Base64 encoded certificate data required to communicate with the cluster"&lt;/span&gt;
  &lt;span class="nx"&gt;sensitive&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;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_certificate_authority_data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add Values for your Variables
&lt;/h3&gt;

&lt;p&gt;Next we need to set values for all the variables we defined in our &lt;code&gt;variables.tf&lt;/code&gt; file. We will do this in a &lt;code&gt;*.tfvars&lt;/code&gt; file, terraform will recognise any file with this extension as holding the values for your variables and look here for those variables values.&lt;/p&gt;

&lt;p&gt;This file usually holds secrets (although non of our variables are secrets of such) and should therefore never be committed to version control in other to avoid exposing your secrets. &lt;/p&gt;

&lt;p&gt;If you copied the .gitignore file in the link at the beginning of this post then your .tfvars file will be ignored by version control.&lt;/p&gt;

&lt;p&gt;Create a new file &lt;code&gt;terraform.tfvars&lt;/code&gt; in the terraform directory and add the code below, substituting it with your details.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;terraform/terraform.tfvars&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vpcname = "&amp;lt;your vpc name&amp;gt;"
cluster_name = "your eks cluster name"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test Configuration
&lt;/h3&gt;

&lt;p&gt;Now we can test our configuration to see what will be created and ensure we have no errors in our script.&lt;/p&gt;

&lt;p&gt;On your terminal, navigate to the terraform directory (where you saved all your terraform scripts) and run the following commands on the terminal:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init

terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2jdqk6fb5vicj1o1of23.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2jdqk6fb5vicj1o1of23.jpg" alt="cli-output" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After running those commands you will see that, just like the image above, terraform will create 63 resources for us when we run the &lt;code&gt;terraform apply&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;We won't apply the configuration just yet, we still have to create some extra roles.&lt;/p&gt;


&lt;h1&gt;
  
  
  Create Policy and Role for Route53 to Assume in the ClusterIssuer Process
&lt;/h1&gt;

&lt;p&gt;While writing the configuration to spin up my VPC and other networking resources as well as my EKS I also added configuration to configure IAM roles and policies for Route 53 with cert-manager.&lt;/p&gt;

&lt;p&gt;I created an IAM role with a trust policy that specifies the Open ID Connect (OIDC) provider and conditions for when the role can be assumed based on the service account and namespace.&lt;/p&gt;

&lt;p&gt;The ClusterIssuer will need these credentials for the certificate issuing process and as a safe way to handle my secrets I will use IAM roles associated with Kubernetes service accounts to manage access to AWS services securely. This is why it is necessary to create this policy and role for Route53 and I did it using terraform.&lt;/p&gt;

&lt;p&gt;You can find the script to create the role &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/blob/main/terraform/route53-role-policy.tf" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Create Roles and Policy for Route53
&lt;/h3&gt;

&lt;p&gt;Down the line we will create a certificate for our domain with LetsEncrypt. This process will need us to create a ClusterIssuer for Let’s Encrypt that uses DNS-01 validation and since we are using AWS Route 53 as our DNS provider we will need to pass Route53 credentials for domain verification.&lt;/p&gt;

&lt;p&gt;The ClusterIssuer will need these credentials for the certificate issuing process and as a safe way to handle our secrets we will use IAM roles associated with Kubernetes service accounts to manage access to AWS services securely. This is why it is necessary to create the next set of policy and role for Route53.&lt;/p&gt;

&lt;p&gt;We will add the configuration to our terraform script.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;terraform/route53-role-policy.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;First we need to create the policy document for the policy that grants the necessary permissions for managing Route 53 DNS records.&lt;/p&gt;

&lt;p&gt;Create a new file &lt;code&gt;route53-role-policy.tf&lt;/code&gt; in the terraform directory and add the below code block to it.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Retrieves the current AWS account ID to dynamically reference it in the policy document&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_caller_identity"&lt;/span&gt; &lt;span class="s2"&gt;"current"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="c1"&gt;# Policy document for the Route53CertManagerPolicy&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_policy_document"&lt;/span&gt; &lt;span class="s2"&gt;"route53_policy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
      &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"route53:GetChange"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:route53:::change/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
      &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"route53:ListHostedZones"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&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;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
      &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"route53:ChangeResourceRecordSets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"route53:ListResourceRecordSets"&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:route53:::hostedzone/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
      &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; 
        &lt;span class="s2"&gt;"route53:ListHostedZonesByName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"sts:AssumeRoleWithWebIdentity"&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;terraform/route53-role-policy.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Now we create the policy, add the next block of code to the &lt;code&gt;terraform/route53-role-policy.tf&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create an IAM policy for Route53 that grants the necessary permissions for managing Route 53 DNS records based on the above policy document&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_policy"&lt;/span&gt; &lt;span class="s2"&gt;"route53_policy"&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;"Route53CertManagerPolicy"&lt;/span&gt;
  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_iam_policy_document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route53_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We will create an IAM role for Service accounts (IRSA) for the Kubernetes service account we will create soon. &lt;/p&gt;

&lt;p&gt;IRSA enables assigning IAM roles to Kubernetes service accounts. This mechanism allows pods to use AWS resources securely, without needing to manage long-lived AWS credentials.&lt;/p&gt;

&lt;p&gt;Before we create the role let's define our trust relationship policy document with a data block like we did with the policy document before.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;terraform/route53-role-policy.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;A trust relationship establishes a trust relationship between your Kubernetes cluster and AWS, allowing Kubernetes service accounts to assume IAM roles.&lt;/p&gt;

&lt;p&gt;Add the below code to your &lt;code&gt;terraform/route53-role-policy.tf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Strip the "https://" prefix from the OIDC issuer URL&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;cluster_oidc_issuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_oidc_issuer_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"https://"&lt;/span&gt;&lt;span class="p"&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="c1"&gt;# Trust relationship policy document for the Route53CertManagerRole we will create&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_policy_document"&lt;/span&gt; &lt;span class="s2"&gt;"oidc_assume_role"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;
  &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
    &lt;span class="nx"&gt;principals&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="s2"&gt;"Federated"&lt;/span&gt;
      &lt;span class="nx"&gt;identifiers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${local.cluster_oidc_issuer}"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"sts:AssumeRoleWithWebIdentity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;test&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"StringEquals"&lt;/span&gt;
      &lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${local.cluster_oidc_issuer}:sub"&lt;/span&gt;
      &lt;span class="nx"&gt;values&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"system:serviceaccount:${var.namespace}:${var.service_account_name}"&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;test&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"StringEquals"&lt;/span&gt;
      &lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${local.cluster_oidc_issuer}:aud"&lt;/span&gt;
      &lt;span class="nx"&gt;values&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"sts.amazonaws.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&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 we need to declare the variables we just called to our &lt;code&gt;variables.tf&lt;/code&gt; file&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;terraform/variables.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"namespace"&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;"The Kubernetes namespace for the service account"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"service_account_name"&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;"The name of the Kubernetes service account"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now we can create the IRSA, do this by adding the following code to the &lt;code&gt;terraform/route53-role-policy.tf&lt;/code&gt; file&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;terraform/route53-role-policy.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create IAM Role for service account&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"Route53CertManagerRole"&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;"Route53CertManagerRole"&lt;/span&gt;
  &lt;span class="nx"&gt;assume_role_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_iam_policy_document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oidc_assume_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Lastly, we need to attach the policy we created above to this newly created role&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;terraform/route53-role-policy.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Add this to your &lt;code&gt;terraform/route53-role-policy.tf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Attach the Route53CertManagerPolicy to the Route53CertManagerRole&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"Route53CertManager"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Route53CertManagerRole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;policy_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route53cmpolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update Your secrets &lt;code&gt;terraform.tfvars&lt;/code&gt; File
&lt;/h3&gt;

&lt;p&gt;We added some new variable so we need to add the values for these variables to our .tfvars file.&lt;/p&gt;

&lt;p&gt;Add the following to your file and substitute the values with your correct values&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;terraform/terraform.tfvars&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;the name of the namespace you will create for your service account&amp;gt;"&lt;/span&gt;
&lt;span class="nx"&gt;service_account_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;your service account name&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;⚠️ &lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Take note of the name you specify here as it must match the name you use for your service account when you create it.&lt;/p&gt;

&lt;p&gt;I will advice you use &lt;code&gt;cert-manager&lt;/code&gt; for both values (namespace and service_account_name), that's what I will be using.&lt;/p&gt;


&lt;h1&gt;
  
  
  Create EKS Resources
&lt;/h1&gt;

&lt;p&gt;I provisioned my EKS cluster at this point to ensure there was no error up to this point.&lt;/p&gt;

&lt;p&gt;Now we can go ahead and create our VPC and EKS cluster with terraform.&lt;/p&gt;

&lt;p&gt;Run the below command:&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;span class="nt"&gt;--auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The screenshots below show a successful deployment of the EKS cluster.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Terraform CLI Showing the Successful Deployment of the Resources&lt;/strong&gt;
&lt;/h4&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F82cd92d1-74c3-4a08-94ec-249b419e5c8d" class="article-body-image-wrapper"&gt;&lt;img alt="4" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F82cd92d1-74c3-4a08-94ec-249b419e5c8d" width="800" height="213"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;AWS Console Showing the EKS Cluster&lt;/strong&gt;
&lt;/h4&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F790b5b01-d46a-4994-ba43-92dead9b1ce1" class="article-body-image-wrapper"&gt;&lt;img alt="eks-cluster" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F790b5b01-d46a-4994-ba43-92dead9b1ce1" width="800" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2Ff0f864fd-b719-4fd3-b64f-7f073db615c9" class="article-body-image-wrapper"&gt;&lt;img alt="cluster-nodes" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2Ff0f864fd-b719-4fd3-b64f-7f073db615c9" width="800" height="247"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;AWS Console Showing the VPC Deployed Along with the EKS Cluster&lt;/strong&gt;
&lt;/h4&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F7c951ef3-5316-46f1-a81b-c78c98e2b1e5" class="article-body-image-wrapper"&gt;&lt;img alt="vpc-console" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F7c951ef3-5316-46f1-a81b-c78c98e2b1e5" width="800" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fom70wrhow2hyf8h9ojc3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fom70wrhow2hyf8h9ojc3.png" alt="vpc-resourcemap" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  Configure HTTPS Using Let’s Encrypt
&lt;/h1&gt;

&lt;p&gt;Before we deploy our application let's go ahead and configure HTTPS using Let's Encrypt. We will be using terraform as well, using the Kubernetes provider and the kubectl provider. We will write a new terraform configuration for this, is this best so that the EKS cluster configuration is separate in other to breakdown the process.&lt;/p&gt;

&lt;p&gt;You can find the terraform scripts for this deployment in the &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/tree/main/k8s-terraform" rel="noopener noreferrer"&gt;K8s-terraform directory here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's write our terraform script together.&lt;/p&gt;


&lt;h3&gt;
  
  
  Add Providers
&lt;/h3&gt;

&lt;p&gt;Create a new directory &lt;code&gt;k8s-terraform&lt;/code&gt;, and create a new file &lt;code&gt;main.tf&lt;/code&gt;. We will add the AWS, kubernetes, kubectl and helm providers as we will need them for the next set of configurations. We will also add our remote backend. We would then need to also add the configurations for those providers.&lt;/p&gt;

&lt;p&gt;Open your main.tf file and add the following to it:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/main.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Add required providers&lt;/span&gt;
&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&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;"hashicorp/aws"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;kubernetes&lt;/span&gt; &lt;span class="p"&gt;=&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;"hashicorp/kubernetes"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;kubectl&lt;/span&gt; &lt;span class="p"&gt;=&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;"gavinbunney/kubectl"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.14"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;helm&lt;/span&gt; &lt;span class="p"&gt;=&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;"hashicorp/helm"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&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;"sockshop-statefiles"&lt;/span&gt; &lt;span class="c1"&gt;# Replace with your bucket name&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"k8s/terraform.tfstate"&lt;/span&gt; &lt;span class="c1"&gt;# Replace with your second folder name&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="nx"&gt;dynamodb_table&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sock-shop-lockfile"&lt;/span&gt; &lt;span class="c1"&gt;# Replace with your DynamoDb table name&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 we can go ahead to add and configure our providers.&lt;/p&gt;


&lt;h3&gt;
  
  
  Configure Provider
&lt;/h3&gt;

&lt;p&gt;Update your main.tf file, add the below to your file&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/main.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Configure the AWS Provider&lt;/span&gt;
&lt;span class="nx"&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
  &lt;span class="nx"&gt;shared_credentials_files&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"~/.aws/credentials"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Configure the kubernetes Provider, the exec will use the aws cli to retrieve the token &lt;/span&gt;
&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_endpoint&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_ca_certificate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_certificate_authority_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;exec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;api_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"client.authentication.k8s.io/v1"&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt;
    &lt;span class="nx"&gt;args&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"eks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"get-token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--cluster-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Configure the kubernetes Provider, the exec will use the aws cli to retrieve the token &lt;/span&gt;
&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"kubectl"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_endpoint&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_ca_certificate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_certificate_authority_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;exec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;api_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"client.authentication.k8s.io/v1"&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt;
    &lt;span class="nx"&gt;args&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"eks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"get-token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--cluster-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Configure the helm Provider using the kubernetes configuration&lt;/span&gt;
&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"helm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;kubernetes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_endpoint&lt;/span&gt;
    &lt;span class="nx"&gt;cluster_ca_certificate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_certificate_authority_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;exec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;api_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"client.authentication.k8s.io/v1"&lt;/span&gt;
      &lt;span class="nx"&gt;command&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt;
      &lt;span class="nx"&gt;args&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"eks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"get-token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--cluster-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&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;We need to retrieve some values from AWS so create another file &lt;code&gt;data.tf&lt;/code&gt; in the k8s-terraform directory and add the below code to it:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/data.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Retrieves the current AWS account ID to dynamically reference it in the policy document&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_caller_identity"&lt;/span&gt; &lt;span class="s2"&gt;"current"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="c1"&gt;# Retrieve details of the current AWS region to dynamically reference it in the configuration&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_region"&lt;/span&gt; &lt;span class="s2"&gt;"current"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="c1"&gt;# Retrieve the eks cluster endpoint from AWS&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_eks_cluster"&lt;/span&gt; &lt;span class="s2"&gt;"cluster"&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Declare Variables
&lt;/h3&gt;

&lt;p&gt;We have referenced some variables in our configuration and so we need to declare them for terraform. To declare the variables we used in the main.tf file, create a new file &lt;code&gt;variables.tf&lt;/code&gt; and add the code:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/variables.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"region"&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;"The region where the VPC will be located"&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;string&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;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"cluster_endpoint"&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;"Endpoint for EKS control plane"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"cluster_certificate_authority_data"&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;"Base64 encoded certificate data required to communicate with the cluster"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let's begin writing the actual configuration&lt;/p&gt;


&lt;h3&gt;
  
  
  Create Kubernetes Service Account for the Cert Manager to use
&lt;/h3&gt;

&lt;p&gt;Earlier, while writing our EKS cluster configuration, we added a configuration to create an IAM role for service account (IRSA). The first thing we will do here was to create the namespace for cert-manager and also create a service account and annotate it with the IAM role.&lt;/p&gt;

&lt;p&gt;Before we create the service account we will create the namespace for cert-manager as our service account will exit in the cert-manager namespace. &lt;/p&gt;

&lt;p&gt;To create a namespace for cert-manager use the &lt;code&gt;kubernetes-namespace&lt;/code&gt; resource. Create a new file &lt;code&gt;cert-manager.tf&lt;/code&gt; in the &lt;code&gt;k8s-terraform&lt;/code&gt; directory and add this code in:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/cert-manager.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Add this code to the file&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create a cert-manager namespace that our service account will use&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_namespace"&lt;/span&gt; &lt;span class="s2"&gt;"cert-manager"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&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;"cert-manager"&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 to the actual creation of the service account, add this code to your configuration&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/cert-manager.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create the service account for cert-manager&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_service_account"&lt;/span&gt; &lt;span class="s2"&gt;"cert_manager"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&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;"cert-manager"&lt;/span&gt;
    &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cert-manager"&lt;/span&gt;
    &lt;span class="nx"&gt;annotations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"eks.amazonaws.com/role-arn"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/Route53CertManagerRole"&lt;/span&gt;
    &lt;span class="p"&gt;}&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;h3&gt;
  
  
  Configure Ingress Controller
&lt;/h3&gt;

&lt;p&gt;Before creating the cert-manager resource we will configure our ingress controller, it's crucial to ensure that your Ingress controller is deployed and running before you create Ingress resources that it will manage.&lt;/p&gt;

&lt;p&gt;If you will like to go ahead, you can find the complete configuration of the ingress controller &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/blob/main/k8s-terraform/ingress.tf" rel="noopener noreferrer"&gt;here&lt;/a&gt;. It will be deployed using helm, using the &lt;code&gt;helm_release&lt;/code&gt; resource in terraform.&lt;/p&gt;

&lt;p&gt;Let's get to writing the configuration together:&lt;/p&gt;

&lt;p&gt;First we will create an &lt;code&gt;ingress-nginx&lt;/code&gt; namespace where the ingress controller will reside, we are creating this manually cos helm has a behaviour of not deleting the namespaces it creates.&lt;/p&gt;

&lt;p&gt;Create a new file &lt;code&gt;ingress.tf&lt;/code&gt; in the k8s-terraform directory and add the code, this is the code to create both the namespace and deploy the ingress controller using helm.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/ingress.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create the ingress-nginx namespace&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_namespace"&lt;/span&gt; &lt;span class="s2"&gt;"ingress-nginx"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&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;"ingress-nginx"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Use helm to create an nginx ingress controller&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"helm_release"&lt;/span&gt; &lt;span class="s2"&gt;"ingress-nginx"&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;"nginx-ingress"&lt;/span&gt;
  &lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://kubernetes.github.io/ingress-nginx"&lt;/span&gt;
  &lt;span class="nx"&gt;chart&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ingress-nginx"&lt;/span&gt;
  &lt;span class="nx"&gt;namespace&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ingress-nginx"&lt;/span&gt;
  &lt;span class="nx"&gt;create_namespace&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;cleanup_on_fail&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;force_update&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;timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6000&lt;/span&gt;

  &lt;span class="nx"&gt;set&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;"controller.service.name"&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;"ingress-nginx-controller"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;set&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;"controller.service.type"&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;"LoadBalancer"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;set&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;"controller.service.annotations.service&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.beta&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.kubernetes&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.io/aws-load-balancer-connection-idle-timeout"&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;"3600"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;set&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;"controller.publishService.enabled"&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;"true"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;set&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;"controller.config.cleanup"&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;"true"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;set&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;"controller.extraArgs.default-ssl-certificate"&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;"sock-shop/${var.domain}-tls"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;kubernetes_namespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ingress-nginx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kubernetes_namespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sock-shop&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;h3&gt;
  
  
  Declare Variables
&lt;/h3&gt;

&lt;p&gt;Declare the domain name variable in the variables.tf file&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/variables.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"domain"&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;"The domain name to access your application from and use in the creation of your SSL certificate"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure Cert-Manager
&lt;/h3&gt;

&lt;p&gt;After configuring the ingress controller, the next thing to do is to configure the cert-manager, I did this also using helm. Find the configuration &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/blob/main/k8s-terraform/cert-manager.tf" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we will deploy cert-manager using a helm chart with the &lt;code&gt;helm_release&lt;/code&gt; resource in terraform. &lt;/p&gt;

&lt;p&gt;Add this code to the &lt;code&gt;k8s-terraform/cert-manager.tf&lt;/code&gt; file&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/cert-manager.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Use Helm to deploy the cert-manager&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"helm_release"&lt;/span&gt; &lt;span class="s2"&gt;"cert_manager"&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;"cert-manager"&lt;/span&gt;
  &lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://charts.jetstack.io"&lt;/span&gt;
  &lt;span class="nx"&gt;chart&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cert-manager"&lt;/span&gt;
  &lt;span class="nx"&gt;namespace&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cert-manager"&lt;/span&gt;
  &lt;span class="nx"&gt;create_namespace&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;cleanup_on_fail&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;force_update&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;set&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;"installCRDs"&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;"true"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;set&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;"serviceAccount.create"&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;"false"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;set&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;"serviceAccount.name"&lt;/span&gt;
    &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;kubernetes_service_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert_manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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="nx"&gt;set&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;"securityContext.fsGroup"&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;"1001"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;set&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;"controller.config.cleanup"&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;"true"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;set&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;"helm.sh/resource-policy"&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;"delete"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;kubernetes_service_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert_manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kubernetes_namespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert-manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;helm_release&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ingress-nginx&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;h3&gt;
  
  
  RBAC (Role-based access control )
&lt;/h3&gt;

&lt;p&gt;In order to allow cert-manager issue a token using your ServiceAccount you must deploy some RBAC (Role-based access control ) to the cluster. Find the complete code &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/blob/main/k8s-terraform/role-roleBinding.tf" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a new file &lt;code&gt;role-roleBinding.tf&lt;/code&gt; in the k8s-terraform directory and add the following to the file:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/role-roleBinding.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Deploy some RBAC to the cluster&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubectl_manifest"&lt;/span&gt; &lt;span class="s2"&gt;"cert-manager-tokenrequest"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;yaml_body&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;YAML&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cert-manager-tokenrequest
  namespace: cert-manager
rules:
  - apiGroups: [""]
    resources: ["serviceaccounts/token"]
    resourceNames: ["cert-manager"]
    verbs: ["create", "update", "delete", "get", "list", "watch"]
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Deploy a RoleBinding to the cluster&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubectl_manifest"&lt;/span&gt; &lt;span class="s2"&gt;"cmtokenrequest"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;yaml_body&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;YAML&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cert-manager-cert-manager-tokenrequest
  namespace: cert-manager
subjects:
  - kind: ServiceAccount
    name: cert-manager
    namespace: cert-manager
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cert-manager-tokenrequest
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure ClusterIssuer
&lt;/h3&gt;

&lt;p&gt;Next we will configure the ClusterIssuer manifest file with the &lt;code&gt;kubectl_manifest&lt;/code&gt; resource from the kubectl provider so that terraform can adequately use it in the certificate issuing process. We will use the DNS01 resolver instead of HTTP01&lt;/p&gt;

&lt;p&gt;I had wanted to use the &lt;code&gt;kubernetes_manifest&lt;/code&gt; resource from the kubernetes provider even though I knew it would require two stages of the &lt;code&gt;terraform apply&lt;/code&gt; command as the cluster has to be accessible at plan time and thus cannot be created in the same apply operation, another limitation of the &lt;code&gt;kubernetes_manifest&lt;/code&gt; resource is that it doesn't support having multiple resources in one manifest file, to circumvent this you could either break down your manifest files into their own individual files (but where's the fun in that) or use a &lt;code&gt;for_each&lt;/code&gt; function to loop through the single file.&lt;/p&gt;

&lt;p&gt;However from research I was able to discover that the kubectl's &lt;code&gt;kubectl_manifest&lt;/code&gt; resource handles manifest files better and allows for a single stage run of the &lt;code&gt;terraform apply&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Find the &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/blob/main/k8s-terraform/cluster-issuer.tf" rel="noopener noreferrer"&gt;ClusterIssuer configuration file here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To configure our ClusterIssuer create a new file &lt;code&gt;cluster-issuer.tf&lt;/code&gt; in the &lt;code&gt;k8s-terraform&lt;/code&gt; directory and add the below code to it.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/cluster-issuer.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create the Cluster Issuer for the production environment&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubectl_manifest"&lt;/span&gt; &lt;span class="s2"&gt;"cert_manager_cluster_issuer"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;yaml_body&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;YAML&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: ${var.email}
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - dns01:
        route53:
          region: ${data.aws_region.current.name}
          hostedZoneID: ${data.aws_route53_zone.selected.zone_id}
        auth:
            kubernetes:
              serviceAccountRef: 
                name: cert-manager
                namespace: cert-manager
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;helm_release&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert_manager&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;h3&gt;
  
  
  Declare Variables
&lt;/h3&gt;

&lt;p&gt;Update your variable.tf file by adding the following to it:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/variables.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"email"&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;"The email address to use in the creation of your SSL certificate"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create Certificate
&lt;/h3&gt;

&lt;p&gt;To create the certificate we will use the &lt;code&gt;kubectl_manifest&lt;/code&gt; resource to define our manifest file for the certificate creation. You can find my certificate manifest file &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/blob/main/k8s-terraform/certificate.tf" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a new file &lt;code&gt;certificate.tf&lt;/code&gt; in the k8s-terraform directory and add the below code to it.&lt;/p&gt;

&lt;p&gt;First we will create a sock-shop namespace as this is the namespace where we want our certificate to be saved. Our certificate will be in this namespace as this is the namespace where our application will be in, the application and the certificate have to be in the same namespace so that the SSl certificate can apply correctly to our site.&lt;/p&gt;

&lt;p&gt;The creation of the certificate will depend on the sock-shop namespace and so the &lt;code&gt;depends_on&lt;/code&gt; argument is added to the configuration.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/certificate.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create a sock-shop namespace that our service account will use&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_namespace"&lt;/span&gt; &lt;span class="s2"&gt;"sock-shop"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&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;"sock-shop"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Resource to create the certificate&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubectl_manifest"&lt;/span&gt; &lt;span class="s2"&gt;"cert_manager_certificate"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;yaml_body&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;YAML&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: ${var.domain}-cert
  namespace: sock-shop  
spec:
  secretName: ${var.domain}-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
    group: cert-manager.io
  commonName: ${var.domain}
  dnsNames:
    - ${var.domain}
    - "*.${var.domain}"
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;&lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;kubernetes_namespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sock-shop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kubectl_manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert_manager_cluster_issuer&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;h3&gt;
  
  
  Declare Variables
&lt;/h3&gt;

&lt;p&gt;We have referenced some more variables in our configuration so we will declare them for terraform as usual. Update your &lt;code&gt;variables.tf&lt;/code&gt; file accordingly&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;terraform/variables.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"domain"&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;"The domain name to access your application from and use in the creation of your SSL certificate"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Configure Ingress
&lt;/h1&gt;

&lt;p&gt;Now that we have configured Cert Manager, Cluster Issuer and Certificate we need to setup our Ingress Controller and Ingress resource that will allow us access to our application, we will also be doing this using our terraform configuration.&lt;/p&gt;

&lt;p&gt;I decided to use the &lt;code&gt;nginx-ingress&lt;/code&gt; helm chart with the aid of the &lt;code&gt;helm-release&lt;/code&gt; resource from the helm provider.&lt;/p&gt;

&lt;p&gt;Find my &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/blob/main/k8s-terraform/ingress.tf" rel="noopener noreferrer"&gt;ingress configuration here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a new file &lt;code&gt;ingress.tf&lt;/code&gt; in the &lt;code&gt;k8s-terraform&lt;/code&gt; directory and add the code below to it.&lt;/p&gt;

&lt;p&gt;Our Ingress will be located in the &lt;code&gt;ingress-nginx&lt;/code&gt; namespace and so we will create the namespace first. &lt;/p&gt;

&lt;p&gt;After creating the namespace we will create the ingress controller that controls the ingress resources and creates a LoadBalancer. The creation of the ingress controller will depend on the ingress-nginx and the sock-shop namespace and so the &lt;code&gt;depends_on&lt;/code&gt; argument is added to the configuration.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/ingress.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create the ingress-nginx namespace&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_namespace"&lt;/span&gt; &lt;span class="s2"&gt;"ingress-nginx"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&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;"ingress-nginx"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Use helm to create an nginx ingress controller&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"helm_release"&lt;/span&gt; &lt;span class="s2"&gt;"ingress-nginx"&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;"nginx-ingress"&lt;/span&gt;
  &lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://kubernetes.github.io/ingress-nginx"&lt;/span&gt;
  &lt;span class="nx"&gt;chart&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ingress-nginx"&lt;/span&gt;
  &lt;span class="nx"&gt;namespace&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ingress-nginx"&lt;/span&gt;
  &lt;span class="nx"&gt;create_namespace&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;cleanup_on_fail&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;force_update&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;timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6000&lt;/span&gt;

  &lt;span class="nx"&gt;set&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;"controller.service.name"&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;"ingress-nginx-controller"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;set&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;"controller.service.type"&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;"LoadBalancer"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;set&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;"controller.service.annotations.service&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.beta&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.kubernetes&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.io/aws-load-balancer-connection-idle-timeout"&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;"3600"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;set&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;"controller.publishService.enabled"&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;"true"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;set&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;"controller.config.cleanup"&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;"true"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;set&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;"controller.extraArgs.default-ssl-certificate"&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;"sock-shop/${var.domain}-tls"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;kubernetes_namespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ingress-nginx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kubernetes_namespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sock-shop&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 we can then create the actual ingress, this will be located in the &lt;code&gt;sock-shop&lt;/code&gt; namespace as our resources will be located there and for effective flow of traffic the ingress needs to be in the same namespace as the application. Add the below code to the &lt;code&gt;k8s-terraform/ingress.tf&lt;/code&gt; file.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/ingress.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create an Ingress resource using the kubectl_manifest resource&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubectl_manifest"&lt;/span&gt; &lt;span class="s2"&gt;"ingress"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;kubectl_manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert_manager_cluster_issuer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kubectl_manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert_manager_certificate&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;yaml_body&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;YAML&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress
  namespace: sock-shop
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    certmanager.k8s.io/acme-challenge-type: dns01
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  tls:
  - hosts:
    - ${var.domain}
    - "www.${var.domain}"
  secretName: "${var.domain}-tls"
  rules:
  - host: ${var.domain}
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: front-end
            port:
              number: 80
  - host: "www.${var.domain}"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: front-end
            port:
              number: 80
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connect Domain to LoadBalancer
&lt;/h3&gt;

&lt;p&gt;The Ingress-controller will create a LoadBalancer that give us an external IP to use in accessing our resources and we will point our domain to it by creating an &lt;code&gt;A&lt;/code&gt; record.&lt;/p&gt;

&lt;p&gt;I used this LoadBalancer to create two A records, one for my main domain name and the other for a wildcard (*) for my subdomains, this will enable us access the sock shop application from our domain.&lt;/p&gt;

&lt;p&gt;Remember the hosted zone we created at the beginning of this project? We need information about it to be available to terraform so we need to retrieve the data from the hosted zone using the data block.&lt;/p&gt;

&lt;p&gt;We will also retrieve the name of the LoadBalancer created by our ingress controller. When I was using the exact name created by the ingress controller, I wss getting an error that the LoadBalancer's name was too long so I retrieved the name and split it to make it shorter. It is all shown in the code block below.&lt;/p&gt;

&lt;p&gt;Add the following to you &lt;code&gt;k8s-terraform/data.tf&lt;/code&gt; file&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/data.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Retrieve the Route53 hosted zone for the domain&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_route53_zone"&lt;/span&gt; &lt;span class="s2"&gt;"selected"&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Retrieve the ingress load balancer hostname&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_service"&lt;/span&gt; &lt;span class="s2"&gt;"ingress-nginx-controller"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&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;"nginx-ingress-ingress-nginx-controller"&lt;/span&gt;
    &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ingress-nginx"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Ensure this data source fetches after the service is created&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;helm_release&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ingress-nginx&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Extract the load balancer name from the hostname&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;ingress_list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kubernetes_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ingress-nginx-controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;load_balancer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;ingress&lt;/span&gt;
  &lt;span class="nx"&gt;lb_hostname&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ingress_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;
  &lt;span class="nx"&gt;lb_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&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;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&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;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lb_hostname&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Data source to fetch the AWS ELB details using the extracted load balancer name&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_elb"&lt;/span&gt; &lt;span class="s2"&gt;"ingress_nginx_lb"&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="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lb_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;As you can see in the above block of code, the original LoadBalancer name was split using the &lt;code&gt;locals&lt;/code&gt; block and the new name is set with a data block.&lt;/p&gt;

&lt;p&gt;Now we can go ahead and create the &lt;code&gt;A&lt;/code&gt; records. Add the following to your &lt;code&gt;k8s-terraform/ingress.tf&lt;/code&gt; file.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/ingress.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Route 53 record creation so that our ingress controller can point to our domain name&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route53_record"&lt;/span&gt; &lt;span class="s2"&gt;"ingress_load_balancer"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_route53_zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_id&lt;/span&gt;  &lt;span class="c1"&gt;# Replace with your Route 53 Hosted Zone ID&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="c1"&gt;# Replace with the DNS name you want&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A"&lt;/span&gt;

  &lt;span class="c1"&gt;# Use the LoadBalancer's external IP or DNS name&lt;/span&gt;
  &lt;span class="nx"&gt;alias&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_elb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ingress_nginx_lb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dns_name&lt;/span&gt;
    &lt;span class="nx"&gt;zone_id&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_elb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ingress_nginx_lb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_id&lt;/span&gt;  &lt;span class="c1"&gt;# zone ID for the alias&lt;/span&gt;
    &lt;span class="nx"&gt;evaluate_target_health&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Route 53 record creation so that our ingress controller can point to subdomains of our domain name&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route53_record"&lt;/span&gt; &lt;span class="s2"&gt;"ingress_subdomain_load_balancer"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_route53_zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_id&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;"*.${var.domain}"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A"&lt;/span&gt;
  &lt;span class="nx"&gt;alias&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_elb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ingress_nginx_lb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dns_name&lt;/span&gt;
    &lt;span class="nx"&gt;zone_id&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_elb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ingress_nginx_lb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_id&lt;/span&gt;
    &lt;span class="nx"&gt;evaluate_target_health&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;h3&gt;
  
  
  Define Outputs
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;k8s-terraform/output.tf&lt;/code&gt; add the following:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/output.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"ingress_load_balancer_dns"&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;"The dns name of the ingress controller's LoadBalancer"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kubernetes_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ingress-nginx-controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;load_balancer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;ingress&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"ingress_load_balancer_zone_id"&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_elb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ingress_nginx_lb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Monitoring, Logging and Alerting
&lt;/h1&gt;

&lt;p&gt;To setup prometheus, grafana, alertmanager and Kibana for monitoring, logging and alerting retrieve the respective manifest files from my Github repo. We will then create two additional ingresses that will exist in the monitoring namespace and the kube-logging namespace so that we can access these dashboards from your subdomain. We will also copy the SSL secret covering the entire domain to the monitoring namespace and the kube-logging namespace.&lt;/p&gt;

&lt;p&gt;Download the alerting manifest files from my github &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/tree/main/alerting" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Download the logging manifest files from my github &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/tree/main/logging" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Download the monitoring manifest files from my github &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/tree/main/monitoring" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add these three directories to your project directory.&lt;/p&gt;

&lt;p&gt;Now we will create two new ingresses.&lt;/p&gt;


&lt;h3&gt;
  
  
  Create a Prometheus and Grafana Ingress
&lt;/h3&gt;

&lt;p&gt;To create an ingress that will point to the prometheus, grafana and alertmanager subdomains, open the &lt;code&gt;k8s-terraform/ingress.tf&lt;/code&gt; file add the below code:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/ingress.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create an Ingress resource using the kubectl_manifest resource for our monitoring resources&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubectl_manifest"&lt;/span&gt; &lt;span class="s2"&gt;"ingress_monitoring"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;kubectl_manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert_manager_cluster_issuer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;null_resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update_secret&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;yaml_body&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;YAML&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress
  namespace: monitoring
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    certmanager.k8s.io/acme-challenge-type: dns01
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  tls:
  - hosts:
    - "prometheus.${var.domain}"
    - "grafana.${var.domain}"
    - "alertmanager.${var.domain}"
  secretName: "${var.domain}-tls"
  rules:
  - host: "prometheus.${var.domain}"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: prometheus
            port:
              number: 9090
  - host: "grafana.${var.domain}"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: grafana
            port:
              number: 80
  - host: "alertmanager.${var.domain}"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: alertmanager
            port:
              number: 9093
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add Secret to the Monitoring and Kube-logging Namespace
&lt;/h3&gt;

&lt;p&gt;If you remember, the SSL certificate we created is in the sock-shop namespace as well as the secret for that certificate but we need the secret to also exist in the monitoring namespace if we will use that SSL for our monitoring sub-domain. &lt;/p&gt;

&lt;p&gt;We can create separate certificates and include them in these namespaces but I do not want to go that route. What we will do is copy the secret from the sock-shop namespace to the monitoring and kube-system namespace.&lt;/p&gt;

&lt;p&gt;We need to fetch the existing secret, using the &lt;code&gt;null_resource&lt;/code&gt; with the &lt;code&gt;local-exec&lt;/code&gt; provisioner. It runs &lt;code&gt;aws eks update-kubeconfig&lt;/code&gt; to configure kubectl then fetches the Secret from the sock-shop namespace, modifies it and saves it in the monitoring and kube-logging namespace. &lt;/p&gt;

&lt;p&gt;The kube-logging namespace is where our elasticsearch, Fluentd and Kibana resources will reside so will will create that first before sending any secret there.&lt;/p&gt;

&lt;p&gt;To create the kube-logging namespace, add the below code to &lt;code&gt;k8s-terraform/ingress.tf&lt;/code&gt; file:&lt;/p&gt;
&lt;h4&gt;
  
  
  k8s-terraform/ingress.tf
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create kube-logging namespace&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes_namespace"&lt;/span&gt; &lt;span class="s2"&gt;"kube-logging"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&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;"kube-logging"&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;Add the following code to your &lt;code&gt;k8s-terraform/data.tf&lt;/code&gt; file:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/data.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Retrieve the certificate secret and copy to the monitoring namespace&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"null_resource"&lt;/span&gt; &lt;span class="s2"&gt;"update_secret"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"local-exec"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;command&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;EOT&lt;/span&gt;&lt;span class="sh"&gt;
    aws eks update-kubeconfig --region us-east-1 --name sock-shop-eks
    kubectl get secret projectchigozie.me-tls -n sock-shop -o yaml | sed 's/namespace: sock-shop/namespace: monitoring/' | kubectl apply -f -
&lt;/span&gt;&lt;span class="no"&gt;    EOT
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;# Ensure this data source fetches after the service is created&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;kubectl_manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert_manager_certificate&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;To create the secret in the kube-logging namespace, we will just repeat the same code changing only the namespace in the metadata.&lt;/p&gt;

&lt;p&gt;Add this code to your k8s-terraform/certificate.tf file&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/certificate.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Retrieve the certificate secret and copy to the kube-logging namespace&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"null_resource"&lt;/span&gt; &lt;span class="s2"&gt;"update_secret_kibana"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"local-exec"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;command&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;EOT&lt;/span&gt;&lt;span class="sh"&gt;
    aws eks update-kubeconfig --region us-east-1 --name sock-shop-eks
    kubectl get secret projectchigozie.me-tls -n sock-shop -o yaml | sed 's/namespace: sock-shop/namespace: kube-logging/' | kubectl apply -f -
&lt;/span&gt;&lt;span class="no"&gt;    EOT
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;# Ensure this data source fetches after the service is created&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;kubectl_manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert_manager_certificate&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;The records for the subdomains are covered by the wildcard (*) record we added so we do not need to add any more A records.&lt;/p&gt;


&lt;h3&gt;
  
  
  Create Ingress for Kibana
&lt;/h3&gt;

&lt;p&gt;To create an ingress that will point to the kibana subdomains open the &lt;code&gt;k8s-terraform/ingress.tf&lt;/code&gt; file add the below code:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;k8s-terraform/ingress.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create an Ingress resource using the kubectl_manifest resource for our kibana resources&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"kubectl_manifest"&lt;/span&gt; &lt;span class="s2"&gt;"ingress_kibana"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;kubectl_manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert_manager_cluster_issuer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;null_resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update_secret_kibana&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;yaml_body&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;YAML&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress
  namespace: kube-logging
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    certmanager.k8s.io/acme-challenge-type: dns01
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  tls:
  - hosts:
    - "kibana.${var.domain}"
  secretName: "${var.domain}-tls"
  rules:
  - host: "kibana.${var.domain}"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: kibana
            port:
              number: 5601
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;⚠️ &lt;strong&gt;NOTE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To use Alertmanager and have it send slack alerts you need to configure your slack webhook, I will make a separate post on how to configure that so that you can get the messages in your slack.&lt;/p&gt;

&lt;p&gt;For now you can skip it by not applying the alerting configurations.&lt;/p&gt;


&lt;h1&gt;
  
  
  Set Environment Variables (Variable Value)
&lt;/h1&gt;

&lt;p&gt;If you noticed, so far we haven't defined any of our variables. We have declared them in our &lt;code&gt;variables.tf&lt;/code&gt; file, we have referenced them in a lot of our configurations but we haven't added any value for any them. &lt;/p&gt;

&lt;p&gt;Typically we do this using a &lt;code&gt;.tfvars&lt;/code&gt; file or by setting them as environment variables. We will be using the latter but with a twist. Most of the variables we used here are the same variables we used in our EKS configuration and so we won't be repeating ourselves.&lt;/p&gt;

&lt;p&gt;When we defined our EKS configuration we defined some outputs and all those outputs are exactly the same with our variables, so we will write a script to take the outputs of the EKS configuration and set them as env vars for this configuration to use. &lt;/p&gt;

&lt;p&gt;Our setup is in such a way that we will build the EKS cluster first, then using the outputs from that deployment we will set the environment variables for our next terraform build to create our ingress resources and SSL certificate.&lt;/p&gt;

&lt;p&gt;We will also export our terraform output values as environment variables to use with kubectl and other configurations. This will aid to make the whole process more automated reducing the manual configurations.&lt;/p&gt;

&lt;p&gt;I wrote different scripts to do this, find the &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/tree/main/scripts" rel="noopener noreferrer"&gt;scripts here&lt;/a&gt;, the first script will create the terraform variables that we will use to run our next deployment, find it &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/blob/main/scripts/1-setTFvars.sh" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This script will extract the key and value from your terraform output and then generate a new script file &lt;code&gt;env.sh&lt;/code&gt; containing the export commands for the Terraform inputs of the next terraform deployment, it also takes into consideration outputs that are marked as sensitive and handles them appropriately and so sets both regular and sensitive Terraform outputs as environment variables.&lt;/p&gt;

&lt;p&gt;To write the scripts, create a new directory &lt;code&gt;scripts&lt;/code&gt; in your root directory. In the scripts directory, create a file &lt;code&gt;1-setTFvars.sh&lt;/code&gt; we will save our script to export our environment variables here.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;scripts/1-setTFvars.sh&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Add the following block of code to your file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c"&gt;# This script sets the Terraform variables from the outputs of the Terraform configuration. Run this script immediately after the first terraform apply that creates the EKS cluster.&lt;/span&gt;

&lt;span class="c"&gt;# Set the path where the script is being run from&lt;/span&gt;
&lt;span class="nv"&gt;RUN_FROM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Define the directory containing your Terraform configuration files by walking backwards to the project root path so that this script works from whatever directory it is run.&lt;/span&gt;
&lt;span class="nv"&gt;ABS_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASH_SOURCE&lt;/span&gt;&lt;span class="p"&gt;[0]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASH_SOURCE&lt;/span&gt;&lt;span class="p"&gt;[0]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;DIR_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ABS_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;PROJECT_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DIR_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;TF_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_PATH&lt;/span&gt;&lt;span class="s2"&gt;/terraform"&lt;/span&gt;

&lt;span class="c"&gt;# Change to the Terraform directory&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TF_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Failed to change directory to &lt;/span&gt;&lt;span class="nv"&gt;$TF_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Check if jq is installed&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; jq &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"jq is not installed. Please install jq and try again."&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Get the Terraform outputs and check if they are non-empty&lt;/span&gt;
&lt;span class="nv"&gt;TF_OUTPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-json&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TF_OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TF_OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"{}"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"No terraform outputs found or outputs are empty. Please run 'terraform apply' first."&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Generate TFenv.sh file with export commands&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TF_OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'to_entries[] | "export TF_VAR_" + .key + "=" + (.value.value | tostring)'&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'echo "Environment variables have been exported and are available for Terraform."'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RUN_FROM&lt;/span&gt;&lt;span class="s2"&gt;/TFenv.sh"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Terraform variables export script has been created as TFenv.sh."&lt;/span&gt;

&lt;span class="c"&gt;# Make it executable&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;u+x &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RUN_FROM&lt;/span&gt;&lt;span class="s2"&gt;/TFenv.sh"&lt;/span&gt;

&lt;span class="c"&gt;# Source the TFenv.sh script to export the variables into the current shell&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Sourcing TFenv.sh..."&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RUN_FROM&lt;/span&gt;&lt;span class="s2"&gt;/TFenv.sh"&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$RUN_FROM&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The script  generates a new script file TFenv.sh containing the export commands and then run the script to use for our next Terraform build.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;NOTE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This script takes about 15 seconds to run, let it run its course.&lt;/p&gt;


&lt;h3&gt;
  
  
  Make Script Executable
&lt;/h3&gt;

&lt;p&gt;You don't necessarily have to make the script executable as we will run it with the &lt;code&gt;source&lt;/code&gt; command but if you want to, save the script as 1-setTFvars.sh and make it executable using the command below:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;u+x 1-setTFvars.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run script
&lt;/h3&gt;

&lt;p&gt;Now we run the script to set our environment variables. Take note of the directory from which you are running the script and adjust the command to reflect the correct path to the script.&lt;/p&gt;

&lt;p&gt;Use the command below:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source &lt;/span&gt;scripts/1-setTFvars.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F0362f976-c83a-4267-a1f2-396cb0a6118e" class="article-body-image-wrapper"&gt;&lt;img alt="TFenv" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F0362f976-c83a-4267-a1f2-396cb0a6118e" width="800" height="115"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The new script generated from this script is not committed to version control as it contains some sensitive values.&lt;/p&gt;


&lt;h1&gt;
  
  
  Create Resources
&lt;/h1&gt;

&lt;p&gt;We've broken down our infrastructure into a two stage build where we will create our EKS cluster first before we configure certificate and ingress, so before you apply this script the first terraform configuration script should have already been deployed.&lt;/p&gt;

&lt;p&gt;Now you are ready to create your resources. Ensure you deploy your EKS cluster using the first Terraform script then make sure you run your first script to set your environment variables. (if you are following this post sequentially you wont have done this step just before getting to this section so skip ahead)&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source &lt;/span&gt;scripts/1-setTFvars.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then apply your terraform script, you should be in the &lt;code&gt;k8s-terraform&lt;/code&gt; directory before running the below commands.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
terraform plan
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now that we have our infrastructure up and running we need to connect kubectl to our cluster so that we can deploy our application and manage our resources.&lt;/p&gt;


&lt;h1&gt;
  
  
  Connect Kubectl to EKS Cluster
&lt;/h1&gt;

&lt;p&gt;Once my EKS Cluster is fully provisioned on AWS and I have deployed my ingress and certificate resources in the cluster, the next thing to do is to connect kubectl to the cluster so that I can use kubectl right from my local machine to define, create, update and delete my Kubernetes resources as necessary.&lt;/p&gt;

&lt;p&gt;The command to do this is shown below:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws eks update-kubeconfig &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;region-code&amp;gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &amp;lt;cluster name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;However since this is an imperative command I decided to create a script out of it for easier automation and reproduction of the process. Find the script &lt;a href="//./scripts/3-connect-kubectl.sh"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before we create the script to connect the kubectl though, will need to set our environment variables for the script to use, this will be the same as the first script with a little modification. Create a new file &lt;code&gt;2-setEnvVars.sh&lt;/code&gt; in the scripts directory and add the below code to the file:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;scripts/2-setEnvVars.sh&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c"&gt;# This script sets environment variables from Terraform outputs for use in connect our kubectl to our cluster. Run this after applying the k8s-terraform configuration.&lt;/span&gt;

&lt;span class="c"&gt;# Set the path where the script is being run from&lt;/span&gt;
&lt;span class="nv"&gt;RUN_FROM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Define the directory containing your Terraform configuration files by walking backwards to the project root path so that this script works from whatever directory it is run.&lt;/span&gt;
&lt;span class="nv"&gt;ABS_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASH_SOURCE&lt;/span&gt;&lt;span class="p"&gt;[0]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASH_SOURCE&lt;/span&gt;&lt;span class="p"&gt;[0]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;DIR_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ABS_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;PROJECT_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DIR_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;TF_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_PATH&lt;/span&gt;&lt;span class="s2"&gt;/terraform"&lt;/span&gt;

&lt;span class="c"&gt;# Change to the Terraform directory&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TF_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Failed to change directory to &lt;/span&gt;&lt;span class="nv"&gt;$TF_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Check if jq is installed&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; jq &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"jq is not installed. Please install jq and try again."&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Get the Terraform outputs and check if they are non-empty&lt;/span&gt;
&lt;span class="nv"&gt;TF_OUTPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-json&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TF_OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TF_OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"{}"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"No terraform outputs found or outputs are empty. Please run 'terraform apply' first."&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Generate env.sh file with export commands&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TF_OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'to_entries[] | "export " + .key + "=" + (.value.value | tostring)'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RUN_FROM&lt;/span&gt;&lt;span class="s2"&gt;/env.sh"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Environment variables export script has been created as env.sh."&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Run the 3-connect-kubectl.sh script now to connect Kubectl to your EKS cluster"&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$RUN_FROM&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You need to run this script before you run the script to connect your kubectl to your cluster. Make your script executable and run it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;u+x 2-setEnvVars.sh
&lt;span class="nb"&gt;source &lt;/span&gt;2-setEnvVars.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Script to connect Kubectl to Cluster
&lt;/h3&gt;

&lt;p&gt;To create the script to connect your kubectl to your cluster create a new file &lt;code&gt;3-connect-kubectl.sh&lt;/code&gt; in the &lt;code&gt;scripts&lt;/code&gt; directory and add the following code block to the file:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;scripts/3-connect-kubectl.sh&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c"&gt;# Source environment variables from a file&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; ./env.sh &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ./env.sh
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Environment variables loaded from env.sh file and successfully set."&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Environment variables file not found."&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Check that AWS CLI is installed, if it isn't stop the script&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; aws &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"AWS CLI is not installed. Please install AWS CLI and try again."&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Check that the kubectl is configured, if it isn't stop the script&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; kubectl &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"kubectl is not installed. Please install kubectl and try again."&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Connect kubectl to your EKS cluster&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Connecting kubectl to your EKS cluster......"&lt;/span&gt;
aws eks update-kubeconfig &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="nv"&gt;$region&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$cluster_name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Make the script Executable and Run it
&lt;/h3&gt;

&lt;p&gt;Save the script and make it executable with the second command, then run it with the second command&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;u+x 3-connect-kubectl.sh
./3-connect-kubectl.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Take note of the directory from which you are running the script and adjust the command to reflect the correct path to the script.&lt;/p&gt;

&lt;p&gt;After running the script you should see that kubectl is connecting with your EKS cluster and you will get a success response if it was successful as seen in the screenshot below.&lt;/p&gt;

&lt;p&gt;When the script is successfully run, our kubectl is connected to our cluster.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F69a4323d-425b-4959-b3da-e0dd572aa11c" class="article-body-image-wrapper"&gt;&lt;img alt="kubectl-connected" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F69a4323d-425b-4959-b3da-e0dd572aa11c" width="800" height="149"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  Deploy Application
&lt;/h1&gt;

&lt;p&gt;Previously I had wanted to deploy the application using terraform but it seems like an overkill seeing as we using a CI/CD pipeline to automate the whole flow, I eventually resolved to use kubectl to deploy the application to the cluster. &lt;/p&gt;

&lt;p&gt;Retrieve the &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/blob/main/app/complete-demo.yaml" rel="noopener noreferrer"&gt;complete-demo.yaml application file&lt;/a&gt; from the project repo which is a combination of all the manifests for all the microservices required for our application to be up and running.&lt;/p&gt;

&lt;p&gt;Download the &lt;a href="https://github.com/ChigozieCO/altschool-capstone-project/blob/main/app/complete-demo.yaml" rel="noopener noreferrer"&gt;complete-demo.yaml file&lt;/a&gt; from my repo and add it to your project directory. For me I created a new directory called &lt;code&gt;app&lt;/code&gt; and saved the application file there.&lt;/p&gt;

&lt;p&gt;Take note of where your saved it cause you will deploy it from that location.&lt;/p&gt;

&lt;p&gt;To deploy our app, run the command below:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; app/complete-demo.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; monitoring/
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; logging/
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; alerting/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With the above commands you have now deployed your application as well as your monitoring, logging and alerting.&lt;/p&gt;
&lt;h3&gt;
  
  
  Verify Deployment
&lt;/h3&gt;

&lt;p&gt;Head on over to your domain to see that the sock-shop application is showing up, this is what it should look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7apt6261jngy7eavtmd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7apt6261jngy7eavtmd.png" alt="application-frontend" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx14g3lopiu06piz3uz7v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx14g3lopiu06piz3uz7v.png" alt="secure-cert" width="800" height="537"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;From the images above you can see that my frontend is secure and the certificate is issued my let's encrypt. If you followed along correctly, yours should be too.&lt;/p&gt;

&lt;p&gt;Check your prometheus and grafana subdomains as well for your monitoring and logging dashboards&lt;/p&gt;
&lt;h4&gt;
  
  
  Prometheus Dashboard
&lt;/h4&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv8gm8wza7xp6322nwx2o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv8gm8wza7xp6322nwx2o.png" alt="Prometheus" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Grafana Dashboard
&lt;/h4&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0sjz7v6ur6i21oknad7w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0sjz7v6ur6i21oknad7w.png" alt="Grafana" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Commands to use in exploring your cluster
&lt;/h3&gt;

&lt;p&gt;We can also verify our setup from our EKS cluster using some kubectl commands:&lt;/p&gt;

&lt;p&gt;🌟 To view the Resources in Cert-manager namespace:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get all &lt;span class="nt"&gt;-n&lt;/span&gt; cert-manager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Resources in Cert-manager namespace
&lt;/h4&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2Fa3f26d0a-5177-4eda-8615-7a386f3a9696" class="article-body-image-wrapper"&gt;&lt;img alt="cert-manager" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2Fa3f26d0a-5177-4eda-8615-7a386f3a9696" width="800" height="306"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;🌟 To view your sock-shop ingress:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get ingress &lt;span class="nt"&gt;-n&lt;/span&gt; sock-shop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Sock-shop ingress
&lt;/h4&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2Fceed9906-2135-4221-ad24-3c58cdbd8674" class="article-body-image-wrapper"&gt;&lt;img alt="sock-shop-ingress" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2Fceed9906-2135-4221-ad24-3c58cdbd8674" width="800" height="89"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;🌟 To see all our application pods:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; sock-shop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Sock-shop pods
&lt;/h4&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F432bba68-d5fe-4a58-a297-95fe31a30e3a" class="article-body-image-wrapper"&gt;&lt;img alt="sock-shop-pod" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F432bba68-d5fe-4a58-a297-95fe31a30e3a" width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;🌟 To see all our application services:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get svc &lt;span class="nt"&gt;-n&lt;/span&gt; sock-shop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Sock-shop-services
&lt;/h4&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F729875ca-5635-45bc-aa1f-cfa654aa731e" class="article-body-image-wrapper"&gt;&lt;img alt="sock-shop-svc" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F729875ca-5635-45bc-aa1f-cfa654aa731e" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;This has been an extremely long post and so we will end here, be on the look out for another post where we setup a CI/CD pipeline with Jenkins for this project. &lt;/p&gt;

&lt;p&gt;If you found this post helpful in anyway, please let me know in the comments and share with your friends. If you have a better way of implementing any of the steps I took here, please do let me know, I love to learn new (~better~) ways of doing things.&lt;/p&gt;

&lt;p&gt;I enjoy breaking down tech topics into simpler concepts and teaching the world (or at least the DEV Community) how to harness the power of the cloud. &lt;/p&gt;

&lt;p&gt;If you'd love to connect on all things cloud engineering or DevOps, then I'd love to as well, drop me a connection request on &lt;a href="https://www.linkedin.com/in/chigozienm/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or follow me &lt;a href="https://github.com/ChigozieCO" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm open to opportunities as well so don't be a stranger, drop a comment, drop a connection, send that DM and let's get cooking. Byeeee until later&lt;/p&gt;

&lt;p&gt;Follow for more DevOps content broken down into simple understandable structure.&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__887485"&gt;
    &lt;a href="/chigozieco" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F887485%2Fd0a1d1a7-f8d7-47e9-8940-b97ae21f54c0.jpg" alt="chigozieco image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/chigozieco"&gt;ChigozieCO&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/chigozieco"&gt;Cloud and DevOps Engineer who's a little bit obsessed with the whole cloud computing scene, always ready to dive into the nitty-gritty of cloud infrastructure, DevOps and Security.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;If this post has helped you, consider supporting me: &lt;a href="https://buymeacoffee.com/chigozieco" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.buymeacoffee.com%2Fassets%2Fimg%2Fcustom_images%2Fyellow_img.png" alt="Buy Me A Coffee" width="170" height="37"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>terraform</category>
      <category>jenkins</category>
      <category>grafana</category>
    </item>
    <item>
      <title>Automate Build, Test &amp; Deploy Processes with GitHub Actions</title>
      <dc:creator>ChigozieCO</dc:creator>
      <pubDate>Mon, 09 Sep 2024 16:22:42 +0000</pubDate>
      <link>https://forem.com/chigozieco/automate-build-test-deploy-processes-with-github-actions-5b4e</link>
      <guid>https://forem.com/chigozieco/automate-build-test-deploy-processes-with-github-actions-5b4e</guid>
      <description>&lt;p&gt;This project is a quick sample project that demonstrates the automation of the build, test, and deployment process of an application to a staging environment on push to the main branch. &lt;/p&gt;

&lt;p&gt;To adequately demonstrate the CI/CD pipeline, we will create a simple Python project with minimal code and then integrate it into GitHub Actions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Create a Simple Python Project
&lt;/h1&gt;

&lt;p&gt;Like I earlier stated, we will create a simple project that we will use in our pipeline. I chose to do this in python for no particular reason, you can use any other programming language you choose, the main of this project is to demonstrate the pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Project Folder
&lt;/h3&gt;

&lt;p&gt;So go ahead and create the project folder and navigate into that folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;automated-testing
&lt;span class="nb"&gt;cd &lt;/span&gt;automated-testing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Write Application File
&lt;/h3&gt;

&lt;p&gt;Now we will write the simple python application. Create a new file &lt;code&gt;app.py&lt;/code&gt; in the project folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the below code block to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, World!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;hello&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 a very simple python "Hello world" function that serves as an example of basic functionality that can be tested in the CI pipeline.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;def hello()&lt;/code&gt; defines a function named hello which takes no arguments. When this function is called, it returns the string "Hello, World!".&lt;/p&gt;

&lt;p&gt;&lt;code&gt;if __name__ == "__main__"&lt;/code&gt; is a standard Python construct used to ensure that certain code only runs when the file is executed directly (not when imported as a module). It acts as an entry point for the script.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;app.py&lt;/code&gt; is run directly (e.g., by running &lt;code&gt;python app.py&lt;/code&gt;), the script will call the hello() function and print the result, which is "Hello, World!".&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Requirements File.
&lt;/h3&gt;

&lt;p&gt;A typical project would have dependencies and in a python project they are usually defined in the &lt;code&gt;requirements.txt&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Create a new file &lt;code&gt;requirements.txt&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this to 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;pytest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create Unit Test
&lt;/h3&gt;

&lt;p&gt;Now we will add a basic test file, &lt;code&gt;test_app.py&lt;/code&gt;, to test the function in app.py. Add the below to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hello&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_hello&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
  &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, World!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we are ready to create our pipeline.&lt;/p&gt;

&lt;h1&gt;
  
  
  Set Up GitHub Actions for CI/CD
&lt;/h1&gt;

&lt;p&gt;To configure GitHub actions we need to create a &lt;code&gt;.github/workflows&lt;/code&gt; folder inside our repo, this is how we notify GitHub of the CI/CD pipeline in our repo.&lt;/p&gt;

&lt;p&gt;Create a new file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .github/workflows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can have multiple pipelines in one repo, so create a file &lt;code&gt;proj.yml&lt;/code&gt; in the &lt;code&gt;.github/workflows&lt;/code&gt; folder. This is where we will define the steps for building, testing, and deploying our Python project.&lt;/p&gt;

&lt;p&gt;Add the below code to your file:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build, Test and Deploy&lt;/span&gt;

&lt;span class="c1"&gt;# Trigger the workflow on pushes to the main branch&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Checkout the code from the repository&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repo&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

    &lt;span class="c1"&gt;# Set up Python environment&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Python&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.x'&lt;/span&gt;

    &lt;span class="c1"&gt;# Install dependencies&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Dependecies&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;python -m pip install --upgrade pip&lt;/span&gt;
        &lt;span class="s"&gt;pip install -r requirements.txt&lt;/span&gt;

    &lt;span class="c1"&gt;# Build (this project deosn't require a build but we will simulate a build by creating a file)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Project&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;mkdir -p build&lt;/span&gt;
        &lt;span class="s"&gt;# Simulate build output by creating a file&lt;/span&gt;
        &lt;span class="s"&gt;touch build/output_file.txt&lt;/span&gt;

    &lt;span class="c1"&gt;# Run tests using pytest&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
        &lt;span class="s"&gt;pytest&lt;/span&gt;

    &lt;span class="c1"&gt;# Upload the build output as an artifact (we created a file in the build step to simulate an artifact)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload build artifact&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-artifact&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build/&lt;/span&gt;

  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-and-test&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;success()&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Download the artifact from the build stage&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download build artifact&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v4&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-artifact&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build/&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Simulate Deployment&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;echo "Deploying to staging..."&lt;/span&gt;
        &lt;span class="s"&gt;ls build/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Breakdown of the CI/CD Pipeline Steps
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trigger on Push to main&lt;/strong&gt;: The pipeline is triggered whenever there is a push to the main branch.&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Checkout Code&lt;/strong&gt;: This step uses GitHub’s checkout action to pull our code from the repository.&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Set Up Python&lt;/strong&gt;: The pipeline sets up a Python environment on the CI runner (GitHub's virtual machine), ensuring that the correct Python version is used.&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Install Dependencies&lt;/strong&gt;: It installs the required dependencies for our Python project (pytest in this case). This dependency was just added as an example of when a project has dependencies as this particular sample python application does not require any.&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build&lt;/strong&gt;: This stage was also just added for demonstration purposes, supposing this was a JavaScript/Node.js project this is where we would run the &lt;code&gt;npm run build&lt;/code&gt; command and this will create an artifact we can upload and use in the deploy stage.
Since this is a python project and it doesn't really require a build, we will create a file and folder in this stage. The file will serve as our artifact for the deploy stage.&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run Tests&lt;/strong&gt;: It runs the tests using pytest to validate the code.&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Upload Build Artifact&lt;/strong&gt;: After running the tests, the build-and-test stage creates and saves a build artifact (in this case, a simulated output_file.txt in the build folder from the build step). The action &lt;code&gt;upload-artifact&lt;/code&gt; is used to store this artifact. You can replace this with whatever actual build output your project creates.&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deploy Stage&lt;/strong&gt;: Our application will only be deployed if the test was successful which is why I have added the conditionals &lt;code&gt;needs and if&lt;/code&gt;. Using “needs” we can require that the deploy job won’t even run unless the test job is successful. 

The &lt;code&gt;download-artifact&lt;/code&gt; action retrieves the build artifact and the last step "Simulate Deployment" simulates deployment by printing a message and lists the artifact. If this was a live project we would have the actual deployment commands to deploy to a real staging environment here. You can replace the echo and ls commands with actual deployment commands (e.g., deploying to a cloud platform).

This approach ensures that the output from the build-and-test stage is properly passed to the deploy stage, simulating how a real deployment would work with build artifacts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Push to GitHub
&lt;/h1&gt;

&lt;p&gt;If you haven't already, you need to initialize a git repository using the commands below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git init
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Create project as well as CI/CD pipeline"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we push to GitHub. Create a GitHub repository and push your code using the below commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote add origin &amp;lt;your-repo-url&amp;gt;
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Verify the Pipeline
&lt;/h1&gt;

&lt;p&gt;After pushing the code, you can visit the &lt;code&gt;Actions&lt;/code&gt; tab in your GitHub repository. You should see the pipeline triggered, running the steps defined in your proj.yml file.&lt;/p&gt;

&lt;p&gt;If everything is set up correctly, the pipeline will build, test, and simulate deployment. You can changes things around in your project and make new pushes to see the the pipeline works, create errors intentional so you can see how the pipeline works when the tests fail.&lt;/p&gt;

&lt;p&gt;On a successful run this is how your &lt;code&gt;Actions&lt;/code&gt; tab should look.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjymcf1fl4d16jbu6ral8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjymcf1fl4d16jbu6ral8.png" alt="successful-workflow-run" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's it, this setup provides a working example of a CI/CD pipeline for a very basic Python project. If you found this helpful, please share with your connection and if you have any questions, do not hesitate to drop the question in the comments.&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>githubactions</category>
      <category>python</category>
      <category>testing</category>
    </item>
    <item>
      <title>Deploy a Static Website with Route53, CloudFront and AWS Certificate using a Terraform Script</title>
      <dc:creator>ChigozieCO</dc:creator>
      <pubDate>Wed, 10 Jul 2024 13:51:05 +0000</pubDate>
      <link>https://forem.com/chigozieco/deploy-a-static-website-with-route53-cloudfront-and-aws-certificate-using-a-terraform-script-25i8</link>
      <guid>https://forem.com/chigozieco/deploy-a-static-website-with-route53-cloudfront-and-aws-certificate-using-a-terraform-script-25i8</guid>
      <description>&lt;p&gt;Automation is queen in this side of our world, the more of your work you can automate, the better.&lt;/p&gt;

&lt;p&gt;The use of Terraform is very necessary for cloud engineers in order to automate deployments of your infrastructure. Terraform is an infrastructure as code tool that lets you define infrastructure resources in human-readable configuration files that you can version, reuse, and share. You can then use a consistent workflow to safely and efficiently provision and manage your infrastructure throughout its lifecycle.&lt;/p&gt;



&lt;h1&gt;
  
  
  Terraform Provider and Initialize Terraform
&lt;/h1&gt;

&lt;p&gt;You can find the complete terraform configuration code for the infrastructure we will be building today &lt;a href="https://github.com/ChigozieCO/altschool-3rd-semester/tree/main/03-Assignment-02" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To begin I will create a &lt;code&gt;main.tf&lt;/code&gt; file and a &lt;code&gt;provider.tf&lt;/code&gt; file for my root module. The first order of business is to configure the AWS provider for Terraform and initialize to get it ready to deploy AWS resources.&lt;/p&gt;

&lt;p&gt;A provider is used by Terraform to interface with the API of whatever infrastructure you're trying to build. Since we are trying to build AWS infrastructure we will be using the AWS infrastructure for our configuration. If you were building in GCP or Azure you will be using a provider for those cloud services.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;provider.tf&lt;/code&gt; file I will add the terraform block as well as the provider block, the terraform code block will allow terraform use the the AWS API build our infrastructure while the provider block will configure the AWS provider with the necessary credentials.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;provider.tf&lt;/code&gt; file add the following lines of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&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;"hashicorp/aws"&lt;/span&gt;
    &lt;span class="p"&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;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="nx"&gt;shared_credentials_files&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"~/.aws/credentials"&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;The great thing about terraform is that you do not have to have any of these codes memorized as you just start out with terraform, you can always refer to the &lt;a href="https://developer.hashicorp.com/terraform/docs" rel="noopener noreferrer"&gt;Terraform documentation&lt;/a&gt; for help. &lt;/p&gt;

&lt;p&gt;In the terraform block I specified AWS as the required provider with source as &lt;code&gt;hashicorp/aws&lt;/code&gt; but I omitted the &lt;code&gt;version&lt;/code&gt; argument as I would like terraform to download the latest version whenever it is initialized.&lt;/p&gt;

&lt;p&gt;The provider block provides the information needed to access AWS specifically. In the provider block I specified the &lt;code&gt;region&lt;/code&gt; argument, that is the only credential I will be hardcoding in my configuration. As I have already setup my AWS credentials using &lt;code&gt;AWS configure&lt;/code&gt; with the AWS CLI  I added the &lt;code&gt;shared_credentials_file&lt;/code&gt; argument (if you have multiple profiles ensure you include the &lt;code&gt;profile&lt;/code&gt; argument and supply the profile name) so Terraform will use that information to pick up these credentials and make use of them to build our infrastructure.&lt;/p&gt;

&lt;p&gt;For a guide on how to configure your AWS credentials in the AWS CLI, check out &lt;a href="https://dev.to/chigozieco/host-a-static-website-using-amazon-s3-and-serve-it-through-amazon-cloudfront-3om8#configure-aws-cli"&gt;this post&lt;/a&gt; of mine where I take you through the process.&lt;/p&gt;

&lt;p&gt;Now I am ready to run a &lt;code&gt;terraform init&lt;/code&gt; to initialize the project so that terraform can download the provider required for this project and connect to AWS.&lt;/p&gt;

&lt;p&gt;Ensure you are in your project directory and run the below command in your terminal:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdfxrope8a1s5hxmooamf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdfxrope8a1s5hxmooamf.png" alt="tf-init" width="715" height="400"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;h1&gt;
  
  
  Terraform Modules
&lt;/h1&gt;

&lt;p&gt;Terraform modules are a powerful feature that allows you to organize and reuse infrastructure configurations. Modules encapsulate groups of resources that are used together and can be referenced and instantiated multiple times within your Terraform configuration.&lt;/p&gt;

&lt;p&gt;Modules are a great way to follow the &lt;code&gt;Don't Repeat Yourself&lt;/code&gt; (DRY) principle of software development which states that code should be written once and not repeated. Modules encapsulate a set of Terraform config files created to serve a specific purpose.&lt;/p&gt;

&lt;p&gt;Modules are used to create reusable components inside your infrastructure. There are primarily two types of modules depending on how they are written (root and child modules), and depending if they are published or not, we identify two different types as well (local and published).&lt;/p&gt;

&lt;p&gt;Reusability is the best consideration while writing Terraform code. Repetition of the same configuration will be laborious because HCL is a declarative language and can be very wordy. Therefore, for optimal reusability, we should attempt to use modules as much as possible so be sure to define them at the beginning, as much as possible. &lt;/p&gt;

&lt;p&gt;We will be writing our configuration as modules and then run the modules to build the configuration.&lt;/p&gt;

&lt;p&gt;It is recommend to place modules in a &lt;code&gt;modules&lt;/code&gt; directory when locally developing modules but you can name it whatever you like.&lt;/p&gt;

&lt;p&gt;To begin I created the &lt;code&gt;Modules&lt;/code&gt; directory, this is where all my modules will reside.&lt;/p&gt;



&lt;h1&gt;
  
  
  Create S3 Bucket Module
&lt;/h1&gt;

&lt;p&gt;For my S3 bucket module, I created a directory named &lt;code&gt;s3-bucket&lt;/code&gt; in the &lt;code&gt;Modules&lt;/code&gt; directory. In this directory I create  the following files &lt;code&gt;main.tf&lt;/code&gt;, &lt;code&gt;variables.tf&lt;/code&gt;, &lt;code&gt;output.tf&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Create S3 Bucket
&lt;/h2&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;modules/s3-bucket/variables.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;In the &lt;code&gt;variables.tf&lt;/code&gt; I define the bucket name as a variable with the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&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;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The name of the S3 bucket"&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;string&lt;/span&gt;

  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&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="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&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="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;63&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; 
      &lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"^[a-z0-9][a-z0-9-.]*[a-z0-9]$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&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="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The bucket name must be between 3 and 63 characters, start and end with a lowercase letter or number, and can contain only lowercase letters, numbers, hyphens, and dots."&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;The validation simply checks that the bucket name is between 3 and 63 characters, start and end with a lowercase letter or number, and contains only lowercase letters, numbers, hyphens, and dots. This is necessary to prevent any error that AWS might throw as a result of wrong bucket naming convention.&lt;/p&gt;

&lt;p&gt;The significance of using a variable file is for simplicity and easy refactoring of the code. In the event that we need to change the value of that variable, we will only need to change it in one place and the change will be picked up anywhere that variable is made reference to using the &lt;code&gt;.var&lt;/code&gt; notation.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;modules/s3-bucket/main.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Now to the creation of the S3 bucket, we will add the &lt;code&gt;aws_s3_bucket&lt;/code&gt; resource block to the module's &lt;code&gt;main.tf&lt;/code&gt; file as shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create S3 Bucket&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"site-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;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="nx"&gt;force_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bucket name is supplied by a variable and will be substituted with the value at creation.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;modules/s3-bucket/outputs.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;This is the file in which we will output some of the values we will use in the rest of our configurations.&lt;/p&gt;

&lt;p&gt;Go ahead and create a new file called &lt;code&gt;outputs.tf&lt;/code&gt; in the s3 bucket module add the below code to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"bucket_regional_domain_name"&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;"This is the bucket domain name including the region name."&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&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;site-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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add the S3 Bucket to the Root Module
&lt;/h2&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;main.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;To test that this module works we will create an s3 bucket using this module we have just written. Head to the &lt;code&gt;main.tf&lt;/code&gt; file of your root module, outside your &lt;code&gt;Module&lt;/code&gt; directory and enter the below piece of code in the file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"s3-bucket"&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;"./Modules/s3-bucket"&lt;/span&gt;
  &lt;span class="nx"&gt;bucket-name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;variables.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Create three new files also in your root module called &lt;code&gt;variables.tf&lt;/code&gt;, &lt;code&gt;outputs.tf&lt;/code&gt; and &lt;code&gt;terraform.tfvars&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;variables.tf&lt;/code&gt; add the code below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&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;type&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;outputs.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;In the &lt;code&gt;outputs.tf&lt;/code&gt; add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&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;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3-bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;site-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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will make use of this output when creating our cloudfront distribution.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;terraform.tfvars&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;In the &lt;code&gt;terraform.tfvars&lt;/code&gt; file, enter the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bucket-name = "&amp;lt;your unique bucket name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ &lt;strong&gt;NOTE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your &lt;code&gt;.tfvars&lt;/code&gt; file should never be committed to version control, add this file to your &lt;code&gt;.gitignore&lt;/code&gt; file. Check out my &lt;a href="https://github.com/ChigozieCO/altschool-3rd-semester/blob/main/03-Assignment-02/.gitignore" rel="noopener noreferrer"&gt;&lt;code&gt;.gitignore&lt;/code&gt; file&lt;/a&gt; for files to add to yours.&lt;/p&gt;

&lt;p&gt;You can also use &lt;a href="https://www.toptal.com/developers/gitignore/" rel="noopener noreferrer"&gt;this site&lt;/a&gt; to generate your gitignore files for this project and future projects.&lt;/p&gt;

&lt;p&gt;In your terminal, run the &lt;code&gt;terraform init&lt;/code&gt; command again, you must rerun the command when you add a module or change provider. If you fail to run it and run any other terraform command you will get the below error message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flj0aza6c3lxom1lhkitn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flj0aza6c3lxom1lhkitn.png" alt="uninstalled-module-error" width="800" height="154"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can run &lt;code&gt;terraform plan&lt;/code&gt; to see what terraform plans to create in your AWS account. &lt;/p&gt;

&lt;p&gt;To create the bucket, run&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;Whenever you run this command terraform will always ask you if you want to carry out this action, you can either answer yes or no. To avoid this question coming up you can directly include &lt;code&gt;auto approve&lt;/code&gt; in the command as shown below:&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;span class="nt"&gt;--auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you followed along correctly, you would have successfully created an s3 bucket, we will destroy it and continue writing our terraform script as we are not just merely creating a bucket. &lt;/p&gt;

&lt;p&gt;Run the command below to destroy the created bucket:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;





&lt;h2&gt;
  
  
  TF alias for Terraform
&lt;/h2&gt;

&lt;p&gt;Before we continue, I want to set a short alias for terraform as we will need to call terraform a whole lot. Setting terraform alias to be &lt;code&gt;tf&lt;/code&gt; will help simplify things for us when we are calling our commands, so we will no longer need to explicitly call out &lt;code&gt;terraform&lt;/code&gt; but now we call it &lt;code&gt;tf&lt;/code&gt; eg &lt;code&gt;tf apply&lt;/code&gt; instead of &lt;code&gt;terraform apply&lt;/code&gt;. It helps us shorten our command.&lt;/p&gt;

&lt;p&gt;We can do this by setting up an alias in the bash profile.&lt;/p&gt;

&lt;p&gt;To open the bash profile for the terminal, I used the below command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vi ~/.bash_profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where we can set bash configurations, we set our alias by calling alias with the short form as seen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;tf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"terraform"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To now use the command we set as alias we need to run that bash profile script first so that the change is applied.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bash_profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can use tf instead of terraform&lt;/p&gt;

&lt;h2&gt;
  
  
  Upload Assets Into S3 Bucket
&lt;/h2&gt;

&lt;p&gt;Before writing the code to upload our website assets into the bucket we should create a directory and save our assets. I will save this in our root module as &lt;code&gt;web-assets&lt;/code&gt; and add my website assets in there.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;modules/s3-bucket/main.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;We will use the &lt;code&gt;for_each&lt;/code&gt; meta arguments to upload our bucket assets, we are using this approach as we have multiple files to upload. this is useful when you create multiple resources with similar configurations. &lt;/p&gt;

&lt;p&gt;It does not make sense to just copy and paste the Terraform resource blocks with minor tweaks in each block. Doing this only affects the readability and unnecessarily lengthens the IaC configuration files. Add the below code to your s3-bucket module &lt;code&gt;main.tf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Upload objects into the s3 Bucket&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_object"&lt;/span&gt; &lt;span class="s2"&gt;"upload-assets"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fileset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${var.web-assets-path}"&lt;/span&gt;&lt;span class="p"&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;bucket&lt;/span&gt; &lt;span class="p"&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;site-bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;
  &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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;"${var.web-assets-path}/${each.value}"&lt;/span&gt;
  &lt;span class="nx"&gt;content_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mime_types&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&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;each&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;"application/octet-stream"&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;The &lt;code&gt;for-each&lt;/code&gt; will iterate through the files in the website directory. I used the &lt;code&gt;fileset&lt;/code&gt; function to iterates over all files and directories in the specified path, making each file/directory available to the for_each loop in the resource definition. &lt;/p&gt;

&lt;p&gt;The path isn't hardcoded, it is defined as a variable in the &lt;code&gt;variable.tf&lt;/code&gt; file as you will see below.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;for_each&lt;/code&gt; loop over &lt;code&gt;fileset&lt;/code&gt; returns file paths, not key-value pairs, this is why we use &lt;code&gt;each.value&lt;/code&gt; as our key and not &lt;code&gt;each.key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We want the website to recognise each file type for it's correct respective MIME type and display it properly on the website which is why we used the &lt;code&gt;lookup&lt;/code&gt; function in the &lt;code&gt;content_type&lt;/code&gt; argument. &lt;code&gt;lookup(map, key, default)&lt;/code&gt; is a function that searches for key in map and returns the associated value if found. If key is not found, it returns default.&lt;/p&gt;

&lt;p&gt;The regex function extracts the file extension from each.value, which is the file name obtained from fileset in other to determine a more accurate MIME type.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;modules/s3-bucket/variables.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Here we will define the variables we called in the piece of code above, add the below code to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Set the variable for the file path of the files to be uploaded to the bucket&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"web-assets-path"&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;"This is the location of our website files"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"mime_types"&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;"Map of file extensions to MIME types"&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;map&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;".html"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"text/html"&lt;/span&gt;
    &lt;span class="s2"&gt;".css"&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"text/css"&lt;/span&gt;
    &lt;span class="s2"&gt;".png"&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"image/png"&lt;/span&gt;
    &lt;span class="s2"&gt;".jpg"&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"image/jpeg"&lt;/span&gt;
    &lt;span class="s2"&gt;".jpeg"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"image/jpeg"&lt;/span&gt;
    &lt;span class="s2"&gt;".pdf"&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application/pdf"&lt;/span&gt;
    &lt;span class="s2"&gt;"json"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application/json"&lt;/span&gt;
    &lt;span class="s2"&gt;"js"&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application/javascript"&lt;/span&gt;
    &lt;span class="s2"&gt;"gif"&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"image/gif"&lt;/span&gt;
    &lt;span class="c1"&gt;# Add more extensions and MIME types as needed&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;h2&gt;
  
  
  Update Root Module &lt;code&gt;main.tf&lt;/code&gt;, &lt;code&gt;variable.tf&lt;/code&gt; and &lt;code&gt;terraform.tfvars&lt;/code&gt; Files
&lt;/h2&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;main.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;we updated our module and so we need to update our root module configuration as well. Your root module's &lt;code&gt;main.tf&lt;/code&gt; file should now look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"s3-bucket"&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;"./Modules/s3-bucket"&lt;/span&gt;
  &lt;span class="nx"&gt;bucket-name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&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="nx"&gt;web-assets-path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web-assets-path&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;variable.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Your root module's &lt;code&gt;variable.tf&lt;/code&gt; file should now look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&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;type&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;variable&lt;/span&gt; &lt;span class="s2"&gt;"web-assets-path"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;terraform.tfvars&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Your root module's &lt;code&gt;terraform.tfvars&lt;/code&gt; file should now 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;bucket-name = "&amp;lt;your unique bucket name&amp;gt;
web-assets-path = "&amp;lt;the path to your website files (best to supply the absolute path)&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;h1&gt;
  
  
  Create Hosted Zone in Route53
&lt;/h1&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Note&lt;/strong&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Even if you are still eligible for the AWS free tier, the Route53 service is never free. This hosted zone will attract a charge of $0.50 per month.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You need a custom domain name for this step, so if you don't already have one, pause, get one and continue along.&lt;/p&gt;

&lt;p&gt;This step is going to be completed manually, initially I was going to import the resource into terraform after manually creating it but upon further consideration we have no reason to as I wouldn't want Terraform deleting the hosted zone. &lt;/p&gt;

&lt;p&gt;The reason for creating the hosted zone manually is simply because when you create a hosted zone, you are given a new set of name servers which you will need to add to your custom domain configuration. Terraform does not have the infrastructure to complete this step and so your configuration will fail until you manually add your name servers to your custom domain yourself.&lt;/p&gt;

&lt;p&gt;Since we already know this, we will manually create the hosted zone, add the name servers to our custom domain and then, using the terraform &lt;code&gt;data&lt;/code&gt; resource, retrieve details of the created hosted zone into terraform to avoid any issues that might have arisen.&lt;/p&gt;
&lt;h2&gt;
  
  
  Create Hosted Zone
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Open your &lt;a href="https://aws.amazon.com/" rel="noopener noreferrer"&gt;AWS management console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;Services&lt;/code&gt; under the &lt;code&gt;Network and Content delivery&lt;/code&gt; category choose &lt;code&gt;Route53&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;create hosted zone&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It's pretty straightforward from there, enter your domain name in the space for &lt;code&gt;domain name&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;public hosted zone&lt;/code&gt; under &lt;code&gt;type&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You can add a tag and description if you want.&lt;/li&gt;
&lt;li&gt;At the bottom of the page, click on &lt;code&gt;create hosted zone&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb9tjfx7uul1y0m4qxrdc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb9tjfx7uul1y0m4qxrdc.png" alt="hosted-zone" width="595" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once your hosted zone has been created, open it to view details and copy the name servers supplied by AWS.&lt;/li&gt;
&lt;li&gt;Copy each name server and replace those already in our domain name with these new ones.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Retrieve the Details of the Hosted Zone Resource into Terraform Configuration
&lt;/h2&gt;

&lt;p&gt;To add details of our hosted zone resource  to terraform we will create a new module in the &lt;code&gt;Module&lt;/code&gt; directory called &lt;code&gt;route53&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Modules/route53/main.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Add the following code to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Retrieve information about your hosted zone from AWS&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_route53_zone"&lt;/span&gt; &lt;span class="s2"&gt;"created"&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="nx"&gt;var&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code might not look like much but it will retrieve the details of the hosted zone in our AWS account that matches the name we supply and then using it wherever we call that specific &lt;code&gt;data&lt;/code&gt; resource in our configuration.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Modules/route53/variables.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;You know the drill, add the declared variables to keep your code reusable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# domain name variable&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"domain_name"&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;"This is the name of the hosted zone."&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;h1&gt;
  
  
  Create TLS/SSL Certificate and Validate it
&lt;/h1&gt;

&lt;p&gt;This is not a one stage process in terraform, we will need to first create the resource and then validate it with another resource block.&lt;/p&gt;
&lt;h2&gt;
  
  
  Create Certificate
&lt;/h2&gt;

&lt;p&gt;We will create our certificate before our cloudfront distribution as we will use our SSL certificate in our cloudfront distribution. &lt;/p&gt;

&lt;p&gt;As usual, create a &lt;code&gt;certificate&lt;/code&gt; directory in the &lt;code&gt;Module&lt;/code&gt; directory which will house our certificate module. Create 3 new files in that directory &lt;code&gt;main.tf&lt;/code&gt;, &lt;code&gt;variable.tf&lt;/code&gt; and &lt;code&gt;output.tf&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Modules/certificate/main.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create the TLS/SSL certificate&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_acm_certificate"&lt;/span&gt; &lt;span class="s2"&gt;"cert"&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain_name&lt;/span&gt;
  &lt;span class="nx"&gt;validation_method&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validation_method&lt;/span&gt;
  &lt;span class="nx"&gt;subject_alternative_names&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subject_alternative_names&lt;/span&gt;

  &lt;span class="c1"&gt;# Ensure that the resource is rebuilt before destruction when running an update&lt;/span&gt;
  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;create_before_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;h4&gt;
  
  
  &lt;strong&gt;Modules/certificate/variables.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Add the necessary variables to  your variables file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"domain_name"&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;"Domain name for which the certificate should be issued"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"validation_method"&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;"Which method to use for validation."&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;string&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;"DNS"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"subject_alternative_names"&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;"Set of domains that should be SANs in the issued certificate."&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;Modules/certificate/outputs.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Define the outputs we will need to reference in other modules&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cert-arn"&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="nx"&gt;aws_acm_certificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"domain_validation_options"&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="nx"&gt;aws_acm_certificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain_validation_options&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create the ACM Certificate Validation Record
&lt;/h2&gt;

&lt;p&gt;Before we create the resource to validate the certificate, we need to create a DNS record in AWS Route 53, which is used to validate the domain ownership for an AWS ACM certificate. The DNS record details (name, value, type) are obtained from the ACM certificate's domain validation options. &lt;/p&gt;

&lt;p&gt;We will create this record in route53 so head on to your &lt;code&gt;Modules/route53/main.tf&lt;/code&gt; file. Add the following to your file:&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Modules/route53/main.tf&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create DNS record that will be used for our certificate validation&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route53_record"&lt;/span&gt; &lt;span class="s2"&gt;"cert_validation"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;dvo&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain_validation_options&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dvo&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="err"&gt;&amp;gt;&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="nx"&gt;dvo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_record_name&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;dvo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_record_type&lt;/span&gt;
    &lt;span class="nx"&gt;record&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dvo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_record_value&lt;/span&gt;
  &lt;span class="p"&gt;}&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="nx"&gt;each&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="nx"&gt;name&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;each&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="nx"&gt;type&lt;/span&gt;
  &lt;span class="nx"&gt;records&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;each&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="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;ttl&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
  &lt;span class="nx"&gt;zone_id&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_route53_zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;created&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above will create a CNAME record in your domain's hosted zone which will be used to validate the certificate which you created. However if you try to apply the code to create te certificate and create the record at the same time, you will get an error message that looks like the own below&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzv2ss5j80wpwrao6odr7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzv2ss5j80wpwrao6odr7.png" alt="for_each-error" width="800" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is why we will run the &lt;code&gt;terraform apply&lt;/code&gt; command in two stages as you will see eventually.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Modules/route53/variables.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Add the following to your route53 module variables file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"domain_validation_options"&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;"The domain validation options from the ACM certificate."&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;object&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="nx"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;resource_record_name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;resource_record_type&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;resource_record_value&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Validate the Certificate
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;aws_acm_certificate&lt;/code&gt; resource does not handle the certificate validation in terraform, we need to use the &lt;code&gt;aws_acm_certificate_validation&lt;/code&gt; resource to accomplish that.&lt;/p&gt;

&lt;p&gt;As I earlier explained and you saw from the error message, the certificate first needs exist and the value of the &lt;code&gt;domain_validation_options&lt;/code&gt; known first before terraform will honour our for_each statement, this is why we need to first create the certificate, then the record and then verify.&lt;/p&gt;

&lt;p&gt;For the above reason we won't put the validation step in the certificate module but in the Route53 module, therefore open your &lt;code&gt;Route53&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;The code for the actual validation is seen below:&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Modules/route53/main.tf&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Validate the certificate&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_acm_certificate_validation"&lt;/span&gt; &lt;span class="s2"&gt;"validate-cert"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;certificate_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&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="nx"&gt;validation_record_fqdns&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;aws_route53_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert_validation&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fqdn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_route53_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert_validation&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;The &lt;code&gt;depends_on&lt;/code&gt; argument will force terraform to create the &lt;code&gt;aws_route53_record.cert_validation&lt;/code&gt; resource first before attempting to validate our certificate.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Modules/route53/variables.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Add the required variables to the module's variables.tf file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&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;type&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;Modules/route53/outputs.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Add the following to your route53 module outputs.tf file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"dns_records"&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="nx"&gt;aws_route53_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert_validation&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We still have to create the CloudFront Distribution Alias Record, this is the record where we will set our cloudfront distribution domain name as an alias for our custom domain name. We will do this after creating our cloudfront distribution.&lt;/p&gt;



&lt;h1&gt;
  
  
  Create CloudFront Module
&lt;/h1&gt;

&lt;p&gt;Now we can go ahead and create our cloudfront distribution. Create a &lt;code&gt;cloudfront&lt;/code&gt; directory in the &lt;code&gt;Modules&lt;/code&gt; directory and add create &lt;code&gt;main.tf&lt;/code&gt; and &lt;code&gt;variables.tf&lt;/code&gt; files in the directory.&lt;/p&gt;
&lt;h2&gt;
  
  
  Create Origin Access Control - OAC
&lt;/h2&gt;

&lt;p&gt;The first thing we need to do is to create the &lt;code&gt;Origin Access Control&lt;/code&gt; we will use in the configuration of our distribution. Do this by adding the code below to your &lt;code&gt;main.tf&lt;/code&gt; file:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Modules/cloudfront/main.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create the access origin control that will be used in creating our cloudfront distribution with s3 origin&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudfront_origin_access_control"&lt;/span&gt; &lt;span class="s2"&gt;"assign-oac"&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oac-name&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;"An origin access control with s3 origin domain for cloudfront"&lt;/span&gt;
  &lt;span class="nx"&gt;origin_access_control_origin_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin_access_control_origin_type&lt;/span&gt;
  &lt;span class="nx"&gt;signing_behavior&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signing_behavior&lt;/span&gt;
  &lt;span class="nx"&gt;signing_protocol&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signing_protocol&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Modules/cloudfront/variables.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Declare the variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"oac-name"&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;"This is the name of the cloudfront origin Access control with s3 bucket origin domain"&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;string&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;"s3-bucket-oac"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"origin_access_control_origin_type"&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;"The origin type must be the same as the origin domain"&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;string&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;"s3"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"signing_behavior"&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;"Specifies which requests CloudFront signs."&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;string&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;"always"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"signing_protocol"&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;"Determines how CloudFront signs (authenticates) requests."&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;string&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;"sigv4"&lt;/span&gt; &lt;span class="c1"&gt;# The only valid value&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Distribution
&lt;/h2&gt;

&lt;p&gt;Now we can create our distribution. Add the following in the &lt;code&gt;main.tf&lt;/code&gt; file:&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Modules/cloudfront/main.tf&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create CloudFront Distribution&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudfront_distribution"&lt;/span&gt; &lt;span class="s2"&gt;"cdn"&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cdn-domain_name-and-origin_id&lt;/span&gt;
    &lt;span class="nx"&gt;origin_id&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cdn-domain_name-and-origin_id&lt;/span&gt;
    &lt;span class="nx"&gt;origin_access_control_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_cloudfront_origin_access_control&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assign-oac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="p"&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;compress&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;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;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;"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;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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cdn-domain_name-and-origin_id&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="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;"all"&lt;/span&gt;
      &lt;span class="p"&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;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="nx"&gt;var&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="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="nx"&gt;var&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="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_2021"&lt;/span&gt;
    &lt;span class="nx"&gt;cloudfront_default_certificate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;default_root_object&lt;/span&gt;
  &lt;span class="nx"&gt;aliases&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&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;"www.${var.domain_name}"&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;h4&gt;
  
  
  &lt;strong&gt;Modules/cloudfront/variables.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Add the required variables&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"restriction_type"&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;"Method that you want to use to restrict distribution of your content by country"&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;string&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;"none"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"default_root_object"&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;"Object that you want CloudFront to return when an end user requests the root URL."&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;string&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;"index.html"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"domain_name"&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;"your custom Domain name for which the certificate should be issued"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"cdn-domain_name-and-origin_id"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"acm_certificate_arn"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;Modules/cloudfront/outputs.tf&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cloudfront-arn"&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="nx"&gt;aws_cloudfront_distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cdn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cloudfront_domain_name"&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="nx"&gt;aws_cloudfront_distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cdn&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="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cloudfront_hosted-zone_id"&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="nx"&gt;aws_cloudfront_distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cdn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hosted_zone_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;h1&gt;
  
  
  Configure S3 Bucket Permission
&lt;/h1&gt;

&lt;p&gt;Now we need to add the specific bucket permissions, that cloudfront needs to be able to adequately interact with our s3 bucket, to our s3 bucket.&lt;/p&gt;

&lt;p&gt;Head back to your s3 bucket module.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Modules/s3-bucket/main.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;This is the policy that will allow our cloudfront distribution access to our s3 bucket and it's object through it's access origin control.&lt;/p&gt;

&lt;p&gt;Add the code below to our s3 bucket module's main.tf file&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Modules/s3-bucket/main.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Add the permissions needed by cloudfront's origin access control to access the bucket and it's objects&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_policy"&lt;/span&gt; &lt;span class="s2"&gt;"cloudfront-oac-policy"&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;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;site-bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;
  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&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;Sid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AllowCloudFrontServicePrincipal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;Service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cloudfront.amazonaws.com"&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${aws_s3_bucket.site-bucket.arn}/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;Condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;StringLike&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"aws:UserAgent"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Amazon CloudFront"&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&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;h1&gt;
  
  
  Create CloudFront Distribution Alias Record
&lt;/h1&gt;

&lt;p&gt;We will create a new module, specially for this, create a new module called &lt;code&gt;alias&lt;/code&gt;, create two files &lt;code&gt;main.tf&lt;/code&gt; and &lt;code&gt;variables.tf&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Modules/alias/main.tf&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Retrieve information about your hosted zone from AWS&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_route53_zone"&lt;/span&gt; &lt;span class="s2"&gt;"created"&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="nx"&gt;var&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="c1"&gt;# Create an alias that will point to the cloudfront distribution domain name&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route53_record"&lt;/span&gt; &lt;span class="s2"&gt;"alias"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;zone_id&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_route53_zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;created&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_id&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain_name&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A"&lt;/span&gt;

  &lt;span class="nx"&gt;alias&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudfront_domain_name&lt;/span&gt;
    &lt;span class="nx"&gt;zone_id&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudfront-zone-id&lt;/span&gt;
    &lt;span class="nx"&gt;evaluate_target_health&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;h4&gt;
  
  
  &lt;strong&gt;Modules/alias/variables.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Declare the necessary variables as usual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"domain_name"&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;"your custom domain name"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"cloudfront_domain_name"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"cloudfront-zone-id"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;h1&gt;
  
  
  Putting it all Together: Move Modules into the Root Module
&lt;/h1&gt;

&lt;p&gt;It's now time to put our modules to use in building our infrastructure. We do this by calling the module in the &lt;code&gt;main.tf&lt;/code&gt; file of our root module, this is our main configuration file.&lt;/p&gt;

&lt;p&gt;We had previously added our s3-bucket module to our main.tf earlier in the project when we wanted to test out our s3-bucket module, now we will add the rest of our modules to the &lt;code&gt;main.tf&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;main.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Your final configuration in your &lt;code&gt;main.tf&lt;/code&gt; of your root module should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create S3 bucket, upload objects into the bucket and set bucket policy.&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"s3-bucket"&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;"./Modules/s3-bucket"&lt;/span&gt;
  &lt;span class="nx"&gt;bucket-name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&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="nx"&gt;web-assets-path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web-assets-path&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create and validate TLS/SSL certificate&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"certificate"&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;"./Modules/certificate"&lt;/span&gt;
  &lt;span class="nx"&gt;domain_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain_name&lt;/span&gt;
  &lt;span class="nx"&gt;subject_alternative_names&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"www.${var.domain_name}"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create OAC and cloudfront distribution, &lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"cloudfront"&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;"./Modules/cloudfront"&lt;/span&gt;
  &lt;span class="nx"&gt;domain_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain_name&lt;/span&gt;
  &lt;span class="nx"&gt;cdn-domain_name-and-origin_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3-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="nx"&gt;acm_certificate_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;certificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert-arn&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route53&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Import the hosted zone from AWS, create dns records for certificate validation, and create A and CNAME records.&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"route53"&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;"./Modules/route53"&lt;/span&gt;
  &lt;span class="nx"&gt;domain_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain_name&lt;/span&gt;
  &lt;span class="nx"&gt;domain_validation_options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;certificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain_validation_options&lt;/span&gt;
  &lt;span class="nx"&gt;certificate_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;certificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert-arn&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create an alias to point the cloudfront cdn to our domain name.&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"alias"&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;"./Modules/alias"&lt;/span&gt;
  &lt;span class="nx"&gt;domain_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain_name&lt;/span&gt;
  &lt;span class="nx"&gt;cloudfront_domain_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudfront_domain_name&lt;/span&gt;
  &lt;span class="nx"&gt;cloudfront-zone-id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudfront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudfront_hosted-zone_id&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudfront&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;h4&gt;
  
  
  &lt;strong&gt;variables.tf&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Now we will declare the necessary variables, your final variable.tf file should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&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;type&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;variable&lt;/span&gt; &lt;span class="s2"&gt;"web-assets-path"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"domain_name"&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;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;terraform.tfvars&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Add your secrets to your &lt;code&gt;*.tfvars&lt;/code&gt; file like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;bucket-name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;your unique bucket name&amp;gt;
web-assets-path = "&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;website&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;best&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;supply&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;absolute&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="err"&gt;)&amp;gt;&lt;/span&gt;
&lt;span class="nx"&gt;domain_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;your custom domain name&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we are all set to deploy our application.&lt;/p&gt;



&lt;h1&gt;
  
  
  Create the Infrastructure
&lt;/h1&gt;
&lt;h3&gt;
  
  
  Install Modules
&lt;/h3&gt;

&lt;p&gt;First run &lt;code&gt;tf init&lt;/code&gt; to install all the added modules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tf init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhouob6vjbtrpmebokxs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhouob6vjbtrpmebokxs.png" alt="install-modules" width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Validate Configuration
&lt;/h3&gt;

&lt;p&gt;Next you can run the validate command to validate your configuration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tf validate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsh75mr30n6r8cy7e00g5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsh75mr30n6r8cy7e00g5.png" alt="tf-validate" width="582" height="56"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Infrastructure
&lt;/h3&gt;

&lt;p&gt;As I already explained earlier the &lt;code&gt;for_each&lt;/code&gt; function will only iterate on values that are already known at the time the &lt;code&gt;apply&lt;/code&gt; command is , therefore if we were to apply before creating our certificate terraform will thrown an error.&lt;/p&gt;

&lt;p&gt;To avoid this error we will apply in two stages, first with the &lt;code&gt;--target&lt;/code&gt; flag and then apply the whole configuration.&lt;/p&gt;

&lt;p&gt;First run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tf apply &lt;span class="nt"&gt;--target&lt;/span&gt; module.certificate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbsrqa301jv13nvn1vf2s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbsrqa301jv13nvn1vf2s.png" alt="--target-command" width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsntjg8atn0vy1zzq3e7z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsntjg8atn0vy1zzq3e7z.png" alt="--target" width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnqhl7qt3ysn41x259d8r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnqhl7qt3ysn41x259d8r.png" alt="--target-created" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lastly, create the remaining resources:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5v7i9v42xzobrdaj4erm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5v7i9v42xzobrdaj4erm.png" alt="tf-apply" width="637" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo69ifrveydjbnf1ak5e3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo69ifrveydjbnf1ak5e3.png" alt="applied" width="800" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Confirm Build
&lt;/h3&gt;

&lt;p&gt;You can open your AWS console to see that the resources have been built.&lt;/p&gt;

&lt;p&gt;Open your browser and navigate to your custom domain and you will see that your website is showing, here is mine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7go2hqwk7j7r6moloyfu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7go2hqwk7j7r6moloyfu.png" alt="hosted-page" width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;h1&gt;
  
  
  Cleanup
&lt;/h1&gt;

&lt;p&gt;Remember to clean up your environment when you are done. Don't leave the resources running in AWS to avoid unnecessary billing.&lt;/p&gt;

&lt;p&gt;Use the destroy command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tf destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0zzjx1zg69k1u2ifrnr9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0zzjx1zg69k1u2ifrnr9.png" alt="destroy" width="751" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Funogiizyq834u4h9v8qf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Funogiizyq834u4h9v8qf.png" alt="destroyed" width="800" height="135"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If this post has helped you, consider supporting me: &lt;a href="https://buymeacoffee.com/chigozieco" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.buymeacoffee.com%2Fassets%2Fimg%2Fcustom_images%2Fyellow_img.png" alt="Buy Me A Coffee" width="170" height="37"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>devops</category>
      <category>automation</category>
    </item>
    <item>
      <title>Host a Static Website using Amazon S3 and Serve it Through Amazon CloudFront.</title>
      <dc:creator>ChigozieCO</dc:creator>
      <pubDate>Mon, 27 May 2024 15:03:17 +0000</pubDate>
      <link>https://forem.com/chigozieco/host-a-static-website-using-amazon-s3-and-serve-it-through-amazon-cloudfront-3om8</link>
      <guid>https://forem.com/chigozieco/host-a-static-website-using-amazon-s3-and-serve-it-through-amazon-cloudfront-3om8</guid>
      <description>&lt;p&gt;AWS S3 buckets can be used to host static web pages but you would need to make it publicly accessible. This opens your bucket and assets to vulnerabilities, how then can you serve your site to the public while minimizing the risks of being attacked by bad actors?&lt;/p&gt;

&lt;p&gt;We can do this using AWS Cloudfront. You do this by serving your site via cloudfront. In this walkthrough, we will do just that. At no point will we make our bucket public accessible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prereq
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You need to have an AWS account, the services used here are covered by the free tier and so you should not incur any costs. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you are just creating your account ensure you create an IAM user that has administrator privilege (it's not recommended to use the root user for regular day to day activities) as well as an active access key.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You need to have AWS CLI 2 installed and configured for programmatic access to your AWS account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You also need to have an HTML page (the static website you want to host), don't fret you don't have to start designing one if you don't have one handy you can get &lt;a href="https://www.tooplate.com/" rel="noopener noreferrer"&gt;html templates here&lt;/a&gt;. I will be using a template gotten from there as well.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Amazon S3 Bucket
&lt;/h2&gt;

&lt;p&gt;Amazon S3 (Simple Storage Service) is an object storage built to store and retrieve any amount of data from anywhere. It integrates with many other AWS services, is highly available and durable and also highly cost-effective. Amazon S3 also provides easy management features to organize data for websites, mobile applications, backup and restore, and many other applications. &lt;/p&gt;

&lt;p&gt;You can configure a storage bucket to host and serve as a static website, on a static website, individual webpages include static content. They might also contain client-side scripts. &lt;/p&gt;

&lt;p&gt;It however cannot be used to host a dynamic website as a dynamic website relies on server-side processing, including server-side scripts, such as PHP, JSP, or ASP.NET. Amazon S3 does not support server-side scripting, but AWS has other resources for hosting dynamic websites.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure AWS CLI
&lt;/h3&gt;

&lt;p&gt;I will be using the AWS CLI to programmatically create my bucket and upload the resources into the bucket as well as to enable static web hosting.&lt;/p&gt;

&lt;p&gt;⚡ First you need to download the &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;aws cli 2&lt;/a&gt;. From the provided link follow the instructions for your device and follow along with the rest of the project.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;span id="aws"&gt; &lt;strong&gt;NOTE&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;If you want to interact with AWS outside of the AWS Management Console and use the cli as I will be in this walkthrough you need to have programmatic access. You need to create an access key for the IAM user you will be using.&lt;/p&gt;

&lt;p&gt;⚡ Simply navigate to the IAM user and scroll down to the &lt;code&gt;access key&lt;/code&gt; and click on &lt;code&gt;create key&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhzzhv4ew9gh4n6k480b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhzzhv4ew9gh4n6k480b.png" alt="1" width="800" height="102"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the use case select the first option being &lt;code&gt;Command Line Interface (CLI)&lt;/code&gt; and click on &lt;code&gt;next&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;You can choose to add a tag or not (tags will make it easier for you to identify your access key) and then go ahead to create the key.&lt;/p&gt;

&lt;p&gt;⚡ The next thing you need to do is to setup the cli for use. Use the &lt;code&gt;aws configure&lt;/code&gt; command and enter your &lt;code&gt;access key&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;aws configure
AWS Access Key ID &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;****************&lt;/span&gt;W0FM]: &amp;lt;your access key ID&amp;gt;
AWS Secret Access Key &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;****************&lt;/span&gt;okxa]: &amp;lt;your secret access key&amp;gt;
Default region name &lt;span class="o"&gt;[&lt;/span&gt;us-east-1]: &amp;lt;whatever region you want to be your default&amp;gt;
Default output format &lt;span class="o"&gt;[&lt;/span&gt;None]: &amp;lt;you can leave empty by clicking enter&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can run the &lt;code&gt;aws sts get-caller-identity&lt;/code&gt; command to verify that you have configured the CLI correctly. The output of this command will display the present user ID, account and ARN.&lt;/p&gt;

&lt;p&gt;Verify that this is the correct credentials and continue.&lt;/p&gt;

&lt;p&gt;Now you are ready to use the AWS CLI for this walk through. &lt;/p&gt;

&lt;h3&gt;
  
  
  Create S3 Bucket
&lt;/h3&gt;

&lt;p&gt;We will be creating a bucket with the default settings. We want our bucket to be private and to block all public access. &lt;/p&gt;

&lt;p&gt;Because we are serving the site through the content distribution network cloudfront (more on that later) we can afford to block all public access to our bucket as it won't be required.&lt;/p&gt;

&lt;p&gt;To create a bucket via the cli, use the below command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api create-bucket &lt;span class="nt"&gt;--bucket&lt;/span&gt; &amp;lt;your unique bucket name&amp;gt; &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;your preferred aws region&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ &lt;strong&gt;NOTE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your bucket name must be globally unique, no two buckets in the whole world can ever have the same exact name, there must be a variation.&lt;/p&gt;

&lt;p&gt;Also if you set your default AWS region and you're ok with your bucket residing in that region you do not need to specify the region with the &lt;code&gt;--region&lt;/code&gt; flag. Remember that your bucket is region specific so it will only exist in the region you created it.&lt;/p&gt;

&lt;p&gt;I won't be using the region tag as I'm ok using my set default region.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api create-bucket &lt;span class="nt"&gt;--bucket&lt;/span&gt; altschool-sem3-site
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it was successful you should see something that resembles the below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8y58o0hndm6m8e3597ql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8y58o0hndm6m8e3597ql.png" alt="3" width="612" height="108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can list your available buckets using &lt;code&gt;aws s3 ls&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffaakcg18qjf47c0b24kx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffaakcg18qjf47c0b24kx.png" alt="4" width="636" height="44"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Upload Objects into S3
&lt;/h3&gt;

&lt;p&gt;Now we need to upload our static website assets.&lt;/p&gt;

&lt;p&gt;A static webpage is one that is sent to a web browser in its identical stored format. It is sometimes referred to as a flat page or a stationary page.&lt;/p&gt;

&lt;p&gt;Until the site is redesigned or the site administrator makes changes directly in the code, the page will not be altered by anything either the user or the site administrator does on the page.&lt;/p&gt;

&lt;p&gt;Our static website assets are our html, css etc, those files you downloaded from the &lt;a href="https://www.tooplate.com/" rel="noopener noreferrer"&gt;html templates here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Time to upload our objects in the S3 bucket, we do that using the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;cp&lt;/span&gt; /local/file/path s3://bucket-name &lt;span class="nt"&gt;--recursive&lt;/span&gt;

aws s3 &lt;span class="nb"&gt;cp&lt;/span&gt; ./2093_flight/ s3://altschool-sem3-site &lt;span class="nt"&gt;--recursive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recursive flag will help us upload multiple files at the same time, it will ensure that all the files and subdirectories within the specified directory are uploaded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Firwzhjxho0gs35u8h8ce.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Firwzhjxho0gs35u8h8ce.png" alt="5" width="800" height="139"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To verify that you have successfully uploaded the objects we will list the objects available in the bucket.&lt;/p&gt;

&lt;p&gt;Use the command below to do that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;ls &lt;/span&gt;s3://bucket-name

aws s3 &lt;span class="nb"&gt;ls &lt;/span&gt;s3://altschool-sem3-site
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnpt76uqm48ea4mayr250.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnpt76uqm48ea4mayr250.png" alt="6" width="651" height="164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should see a list of the objects you just uploaded into your bucket. From the screenshot above you can see my uploaded objects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Amazon CloudFront
&lt;/h2&gt;

&lt;p&gt;Amazon CloudFront is a content delivery network operated by Amazon Web Services. It is a service that helps you distribute your static and dynamic content quickly and reliably with high speed by using a network of proxy servers to cache content, such as web videos or other bulky media, more locally to consumers, to improve access speed for downloading the content.&lt;/p&gt;

&lt;p&gt;We will use the management console for this, log in with your IAM user and navigate to cloudfront under the Networking &amp;amp; Content Delivery category.&lt;/p&gt;

&lt;p&gt;When you load the cloudfront page click on &lt;code&gt;create distribution&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcrcsxnxsjrnu8p385fzl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcrcsxnxsjrnu8p385fzl.png" alt="7" width="800" height="154"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudFront configurations
&lt;/h3&gt;

&lt;h3&gt;
  
  
  - Origin
&lt;/h3&gt;

&lt;h4&gt;
  
  
  ⚡ Origin Domain
&lt;/h4&gt;

&lt;p&gt;Origins are where you store the original versions of your web content. CloudFront gets your web content from your origins and serves it to viewers via a worldwide network of edge servers.&lt;/p&gt;

&lt;p&gt;The origin domain is the DNS domain name of the Amazon S3 bucket or HTTP server from which you want CloudFront to get objects for this origin.&lt;/p&gt;

&lt;p&gt;To that effect select the S3 Bucket we created earlier, from the dropdown, as your &lt;code&gt;origin domain&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  ⚡ Origin path
&lt;/h4&gt;

&lt;p&gt;Leave this blank, as is.&lt;/p&gt;

&lt;h4&gt;
  
  
  ⚡ Name
&lt;/h4&gt;

&lt;p&gt;This will automatically be filled in when you enter your origin domain&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdai7tay47ji0zpn7pv7t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdai7tay47ji0zpn7pv7t.png" alt="8" width="800" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  ⚡ Origin Access
&lt;/h4&gt;

&lt;p&gt;In other to further restrict the access to our Amazon S3 bucket origin to only specific CloudFront distributions we will set our &lt;code&gt;origin access&lt;/code&gt; to the AWS recommended setting which is &lt;code&gt;Origin access control settings&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once that is selected, we will create a new Origin access control settings.&lt;/p&gt;

&lt;p&gt;Select the &lt;code&gt;Origin access control settings&lt;/code&gt; radio button and then click on &lt;code&gt;Create new OAC&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fydg94uvynqd8by1mv15w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fydg94uvynqd8by1mv15w.png" alt="9" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pop up that appears when you click on create new OAC will be populated with the necessary details, leave it as is and click on &lt;code&gt;Create&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprf4eergw1pvvpn23a2p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprf4eergw1pvvpn23a2p.png" alt="10" width="647" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As soon as this new AOC is created, you will see a warning (same as shown below) telling you that you will need to update your bucket policy with the policy that will be provided after the distribution is created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8nenkeepnae2cee9g2lm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8nenkeepnae2cee9g2lm.png" alt="11" width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  - All Other Config
&lt;/h3&gt;

&lt;p&gt;For the Web Application Firewall, select &lt;code&gt;do not enable security protections&lt;/code&gt;, You could actually choose to enable it if you want I am just trying to be safe and not incur any unexpected costs.&lt;/p&gt;

&lt;p&gt;The last setting you will be changing is the &lt;code&gt;default root object&lt;/code&gt;, type in &lt;code&gt;index.html&lt;/code&gt;. This will allow CloudFront to serve your index page to your site visitors.&lt;/p&gt;

&lt;p&gt;Leave all other configurations in their default settings, those settings work just fine for what we are trying to do.&lt;/p&gt;

&lt;p&gt;Your configuration should look like the screenshots below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4ffobctbz7nppsu44fb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4ffobctbz7nppsu44fb.png" alt="12" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayo7m4hb0ij033xzopa3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayo7m4hb0ij033xzopa3.png" alt="13" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmqewtgv93ug0dksqy5q3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmqewtgv93ug0dksqy5q3.png" alt="14" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6idsoiuxo7y7r2qzbu0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6idsoiuxo7y7r2qzbu0.png" alt="15" width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8su4tvl79qhz2fpw2wvq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8su4tvl79qhz2fpw2wvq.png" alt="16" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7lwtorpj6sx9t8t2sobw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7lwtorpj6sx9t8t2sobw.png" alt="17" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scroll down to the bottom of the page and click &lt;code&gt;create distribution&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ftmw3rqgfn89fgf4rht.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ftmw3rqgfn89fgf4rht.png" alt="18" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy the provided policy, you can see it highlighted in yellow on your screen, see screenshot above for the item labelled &lt;code&gt;1&lt;/code&gt; to know where to locate it.&lt;/p&gt;

&lt;p&gt;After copying the policy click on the link to take you to the bucket in order to update the permissions with the policy you just copied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update Bucket Policy With Necessary Permission
&lt;/h2&gt;

&lt;p&gt;You could either manually navigate to the S3 bucket we have been using for this project or you could click on the link in the above picture labelled 2 to take you directly to the bucket.&lt;/p&gt;

&lt;p&gt;Once the bucket is open, click on the permissions tab. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmhh57casq50xc6kyhtfd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmhh57casq50xc6kyhtfd.png" alt="19" width="800" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scroll down to &lt;code&gt;Bucket Policy&lt;/code&gt; and click on &lt;code&gt;edit&lt;/code&gt; to add the permissions we copied at the end of the cloudfront distribution creation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpwy2eogboj7kxpfvohpn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpwy2eogboj7kxpfvohpn.png" alt="20" width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The copied policy is shown below, ensure you use the one you copied from your cloudfront console as it will carry your unique ARN.&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;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2008-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PolicyForCloudFrontPrivateContent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowCloudFrontServicePrincipal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&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;span class="nl"&gt;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cloudfront.amazonaws.com"&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;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::altschool-sem3-site/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&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;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&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;span class="nl"&gt;"AWS:SourceArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:cloudfront::&amp;lt;redacted&amp;gt;:distribution/EOE3G4O3YYZSA"&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;span class="p"&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;span class="p"&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;Now save your changes.&lt;/p&gt;

&lt;p&gt;Your bucket policy should now look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwnprm3b8m7mesnadfru.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwnprm3b8m7mesnadfru.png" alt="21" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Access Your Site
&lt;/h2&gt;

&lt;p&gt;Head back to your cloudfront console and retrieve your distribution domain name as shown in the image below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fua9ac7pjn6tbmq0wchyj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fua9ac7pjn6tbmq0wchyj.png" alt="22" width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the address you will enter in your browser to be able to see your website.&lt;/p&gt;

&lt;p&gt;We can see from the screenshot below that cloudfront is serving the webpage correctly without have to unblock public access of our bucket.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6n9tvzyfp806j7so9mw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6n9tvzyfp806j7so9mw.gif" alt="rendered-page" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's it!! We have successfully served a static webpage using Amazon S3 and Amazon CloudFront.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Automate the Deployment of a LAMP Stack &amp; Laravel App with a Script and Ansible</title>
      <dc:creator>ChigozieCO</dc:creator>
      <pubDate>Thu, 16 May 2024 17:53:07 +0000</pubDate>
      <link>https://forem.com/chigozieco/automate-the-deployment-of-a-lamp-stack-laravel-app-with-a-script-and-ansible-hf0</link>
      <guid>https://forem.com/chigozieco/automate-the-deployment-of-a-lamp-stack-laravel-app-with-a-script-and-ansible-hf0</guid>
      <description>&lt;p&gt;Let's dive into one of the most recent projects I did for a cloud engineering endeavour. We were tasked to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automate the provisioning of two Ubuntu-based servers, named “Master” and “Slave”, using Vagrant.&lt;/li&gt;
&lt;li&gt;On the Master node, create a bash script to automate the deployment of a LAMP (Linux, Apache, MySQL, PHP) stack.&lt;/li&gt;
&lt;li&gt;This script should clone a &lt;a href="https://github.com/laravel/laravel" rel="noopener noreferrer"&gt;PHP application from GitHub&lt;/a&gt;, install all necessary packages, and configure Apache web server and MySQL. &lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure the bash script is reusable and readable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using an Ansible playbook:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Execute the bash script on the Slave node and verify that the PHP application is accessible through the VM’s IP address (take screenshot of this as evidence)&lt;/li&gt;
&lt;li&gt;Create a cron job to check the server’s uptime every 12 am.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Provision VMs
&lt;/h1&gt;

&lt;p&gt;To begin this project I need to provision two Ubuntu servers named "Master" and "Slave" using vagrant.&lt;/p&gt;

&lt;p&gt;I will be provisioning ubuntu 22.04 LTS for the Master and the slave and I will use a multi-machine environment, this basically means that I will define two VMs in one vagrantfile.&lt;/p&gt;

&lt;p&gt;The first this I do is initialize the box, the box I'm using is &lt;code&gt;ubuntu/jammy64&lt;/code&gt; and because I am using a multi-machine environment I will pass the &lt;code&gt;-m&lt;/code&gt; (minimal) flag so that the VagrantFile is created without all the comments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant init &lt;span class="nt"&gt;-m&lt;/span&gt; ubuntu/jammy64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4amb14w5qc06tiaa0n3a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4amb14w5qc06tiaa0n3a.png" alt="Vagrantup" width="756" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I open the VagrantFile to edit it, we can see that it has just the basic configuration without all the comments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vi VagrantFile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59mrprnr58lpxs5nscbk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59mrprnr58lpxs5nscbk.png" alt="Vagrantfile" width="663" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To configure my VMs, I will enter the below code into my VagrantFile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="s2"&gt;"master"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;master&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;master&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu/jammy64"&lt;/span&gt;
    &lt;span class="n"&gt;master&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"master"&lt;/span&gt;

    &lt;span class="n"&gt;master&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"private_network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s2"&gt;"dhcp"&lt;/span&gt;
    &lt;span class="n"&gt;master&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="ss"&gt;:forwarded_port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;guest: &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="mi"&gt;2030&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="s2"&gt;"ssh"&lt;/span&gt;

    &lt;span class="n"&gt;master&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"virtualbox"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Master"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="s2"&gt;"slave"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;slave&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;slave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu/jammy64"&lt;/span&gt;
    &lt;span class="n"&gt;slave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"slave"&lt;/span&gt;

    &lt;span class="n"&gt;slave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"private_network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s2"&gt;"dhcp"&lt;/span&gt;
    &lt;span class="n"&gt;slave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="ss"&gt;:forwarded_port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;guest: &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="mi"&gt;2032&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="s2"&gt;"ssh"&lt;/span&gt;

    &lt;span class="n"&gt;slave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"virtualbox"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Slave"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;a href="https://github.com/ChigozieCO/laravel-application-deployment/blob/main/Vagrantfile" rel="noopener noreferrer"&gt;VagrantFile&lt;/a&gt; I simply specified the type of base box with which each VM would be created, I ensured they used a private network and they get their IP address from the DHCP server. I also specified the name and hostname of each VM.&lt;/p&gt;

&lt;p&gt;Another thing I did was to hard code the host SSH port so that I deal with the SSH clash that will happen, from the jump, another reason I did this is so that the port doesn't conflict with other VMs I have on my laptop.&lt;/p&gt;

&lt;p&gt;To provision the VMs I ran the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the images below you can see that both my master and slave machines were successfully provisioned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Febv2j2q8nq1hxp2a0bun.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Febv2j2q8nq1hxp2a0bun.png" alt="Master-server" width="765" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwlsoju65s6w2dj7v3zow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwlsoju65s6w2dj7v3zow.png" alt="Slave-server" width="774" height="265"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To access it and start working with it I SSH into them by specifying their names, as shown below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant ssh master
vagrant ssh slave
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpipx7c3l51vkcsbqm36b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpipx7c3l51vkcsbqm36b.png" alt="master&amp;amp;slave-ssh" width="584" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Bash Script to Deploy LAMP Stack
&lt;/h1&gt;

&lt;p&gt;A LAMP stack is an acronym for the operating system, Linux; the web server, Apache; the database server, MySQL; and the programming language, PHP.&lt;/p&gt;

&lt;p&gt;This is a very common stack developers use to build websites and web applications.&lt;/p&gt;

&lt;p&gt;The task asked that we write a script that would be used to automate the installation of the LAMP stack. With this script the LAMP stack deployment as well as the Laravel application deployment will be fully automated, this script will be written in a way as not to require any user input at all. I will try to explain my logic along the way.&lt;/p&gt;

&lt;p&gt;Find the full script &lt;a href="https://github.com/ChigozieCO/laravel-application-deployment/blob/main/deploylamp.sh" rel="noopener noreferrer"&gt;here&lt;/a&gt;, to create your script create a new file with the vi editor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vi deploylamp.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install Apache
&lt;/h2&gt;

&lt;p&gt;To begin, I want this script to stop running whenever it encounters an error at any point, as the successful deployment of the Laravel application is dependent on all the components of this script being present in the server. To accomplish this we will use the &lt;code&gt;set -e&lt;/code&gt; command, start you script that way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# This command will make the script immediately close if any command exits with a non-zero status &lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we will update the apt repository so that our packages are up to date.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Update apt repository&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We finally get to the beginning of the main event, now we will add the command that installs Apache to our script, as earlier mentioned, apache is a very popular webserver used by developers.&lt;/p&gt;

&lt;p&gt;To install apache add the command below to your script&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install Apache and handle any errors if any&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Installing Apache ===================================================="&lt;/span&gt;
&lt;span class="nb"&gt;echo

sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; apache2 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error installing Apache"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Successfully installed apache ======================================="&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The echo commands are there to inform us every step of the way what the script is up to. &lt;/p&gt;

&lt;p&gt;The command &lt;code&gt;sudo apt install -y apache2 || { echo "Error installing Apache"; exit 1; }&lt;/code&gt; will either install Apache or print an error message depending on whether the installation is successful or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install MySQL
&lt;/h2&gt;

&lt;p&gt;MySQL is a fast, multi-threaded, multi-user, and robust SQL database server. It is intended for mission-critical, heavy-load production systems and mass-deployed software.&lt;/p&gt;

&lt;p&gt;Add the below to your bash script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install MySQL and handle any errors if any&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Installing MySQL ===================================================="&lt;/span&gt;
&lt;span class="nb"&gt;echo

sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; mysql-server &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error installing MySQL"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Successfully installed MySQL ======================================="&lt;/span&gt;
&lt;span class="nb"&gt;echo
echo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the installation is complete, the MySQL server usually starts automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure MySQL
&lt;/h2&gt;

&lt;p&gt;The default configuration of  MySQL is not secure, the root has no password and remote access is possible for the root user, it also comes with a &lt;code&gt;test&lt;/code&gt; database and an anonymous user, these are the configs that we will be changing.&lt;/p&gt;

&lt;p&gt;Ideally we would use the &lt;code&gt;mysql_secure_installation&lt;/code&gt; script to secure the database but that would require user input and I'm trying to keep this LAMP deployment process as unattended as possible and so I will be using a &lt;code&gt;here document&lt;/code&gt; that would provide all the necessary responses to the prompts that the script usually presents.&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;NOTE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;span id="av"&gt;Another thing to note is that for security purposes I won't be hardcoding my root password in the script. Since this script will be run with Ansible I will save the password in Ansible vault. When we get to the Ansible configuration part of this project you will see how we will walk through the process of adding the password to Ansible vault.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Ansible provides a built-in solution called Ansible Vault for encrypting sensitive data. You can create an encrypted file to store the MySQL root password, and then Ansible will decrypt it when needed during playbook execution.&lt;/p&gt;

&lt;p&gt;The way this will work is that ansible will decrypt the password, and pass it as an env variable as &lt;code&gt;MYSQL_ROOT_PASSWORD&lt;/code&gt; (as we instruct it to do in the ansible playbook) which the bash script will then read and use while running the script.&lt;/p&gt;

&lt;p&gt;This way everything is safe and secure. &lt;/p&gt;

&lt;p&gt;Add the below to your script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Configure MySQL Server automatically, we won't hardcode the root password, it will be read from ansible vault&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Now configuring MySQL Server ======================================="&lt;/span&gt;

&lt;span class="c"&gt;# Main MySQL configuration&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mysql &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;
CREATE DATABASE IF NOT EXISTS laravel;
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '&lt;/span&gt;&lt;span class="nv"&gt;$MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="sh"&gt;';
DELETE FROM mysql.user WHERE User='';
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test&lt;/span&gt;&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="sh"&gt;%';
FLUSH PRIVILEGES;
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SQL commands in the above part of the script will essentially do what &lt;code&gt;mysql_secure_installation&lt;/code&gt; does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates a laravel database that the laravel app would use.&lt;/li&gt;
&lt;li&gt;Set a new password for the root user.&lt;/li&gt;
&lt;li&gt;Remove anonymous users.&lt;/li&gt;
&lt;li&gt;Disallow remote root login.&lt;/li&gt;
&lt;li&gt;Remove the test database and access to it.&lt;/li&gt;
&lt;li&gt;Flush privileges to apply the changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lastly we will check if the configuration succeeded or failed and print a message based on the exit code. Add the following to the script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check if the configuration was successful or if there was an error and let us know which it is&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Failed to configure MySQL ====-============================================="&lt;/span&gt;
    &lt;span class="nb"&gt;echo
    exit &lt;/span&gt;1
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Successfully configured MySQl ==================================================="&lt;/span&gt;
    &lt;span class="nb"&gt;echo
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install PHP
&lt;/h2&gt;

&lt;p&gt;PHP is a general-purpose scripting language, well-suited for Web development since PHP scripts can be embedded into HTML.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disable any PHP Module you Might Have Previously Installed
&lt;/h3&gt;

&lt;p&gt;Before installing PHP I will disable any php module I had previously installed, I found this step necessary because while testing my script, after successfully deploying the Laravel application the app didn't come up I kept getting the error shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo7j55wtakr1ob9jec9ud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo7j55wtakr1ob9jec9ud.png" alt="php-version-error" width="746" height="131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Upon further investigation after running &lt;code&gt;apachectl -M | grep php&lt;/code&gt; I found out that apache was using a lower version of php (7.4) not the latest 8.2 I had just installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;apachectl &lt;span class="nt"&gt;-M&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;php
 php7_module &lt;span class="o"&gt;(&lt;/span&gt;shared&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To save you that stress, first disable any old one that might exist on your server before installing the latest version using the if statement below. Add to your script&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check if any PHP module is enabled&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;apachectl &lt;span class="nt"&gt;-M&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s1"&gt;'php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# Disable all PHP modules&lt;/span&gt;
    &lt;span class="nb"&gt;sudo &lt;/span&gt;a2dismod php&lt;span class="k"&gt;*&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"All PHP modules disabled =========================================================="&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"No PHP modules found =========================================================="&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The script uses &lt;code&gt;apachectl -M&lt;/code&gt; to list all enabled Apache modules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If a PHP module is found, the script disables all PHP modules using &lt;code&gt;sudo a2dismod php*&lt;/code&gt;, which disables any module starting with "php". This is a precautionary measure in case there are multiple PHP versions installed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If no PHP modules are found, it prints a message indicating that no PHP modules were found.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Install the Latest Version
&lt;/h3&gt;

&lt;p&gt;Although PHP is available on Ubuntu Linux Apt repository, to be able to get the latest version I will add the OndreJ PPA and install it from there.&lt;/p&gt;

&lt;p&gt;I will also be installing some necessary dependencies that MySQL needs to be able to work with PHP.&lt;/p&gt;

&lt;p&gt;Add the following to the script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install dependencies first&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; software-properties-common apt-transport-https ca-certificates lsb-release
&lt;span class="c"&gt;# Add the OndreJ PPA to allow us install the latest version of PHP&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;add-apt-repository &lt;span class="nt"&gt;-y&lt;/span&gt; ppa:ondrej/php
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="c"&gt;# Install php and necessary modules&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; php8.2 php8.2-mysql php8.2-cli php8.2-gd php8.2-zip php8.2-mbstring php8.2-xmlrpc php8.2-soap php8.2-xml php8.2-curl php8.2-dom &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error installing php and php modules"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Successful installed PHP and necessary modules =================================="&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enable URL Rewriting and the new PHP module
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Enable Apache's URL rewriting and restart Apache&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;a2enmod rewrite
&lt;span class="c"&gt;# Incase mpm_event is enabled disable it and enable mpm_forked as that is what php8.2 needs&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;a2dismod mpm_event &lt;span class="c"&gt;#In one of my tests this was enabled by default but php needed the prefork and it caused errors for me.&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;a2enmod mpm_prefork 
&lt;span class="nb"&gt;sudo &lt;/span&gt;a2enmod php8.2
&lt;span class="nb"&gt;sudo &lt;/span&gt;service apache2 restart
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"URL rewrite and PHP module enabled and Apache restarted ========================================================================"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enabling the rewrite module in Apache (&lt;code&gt;a2enmod rewrite&lt;/code&gt;) is typically necessary for Laravel applications and many other web applications that use URL rewriting for routing and clean URLs.&lt;/p&gt;

&lt;p&gt;Laravel, like many modern PHP frameworks, relies on URL rewriting to route requests through its front controller (&lt;code&gt;index.php&lt;/code&gt;). This allows for cleaner and more expressive URLs without the need for file extensions or query parameters.&lt;/p&gt;

&lt;p&gt;The newly installed PHP 8.2 module won't be automatically enabled after installation. After installing PHP 8.2, we'll still need to enable the PHP 8.2 module for Apache to use it. You can do this using the &lt;code&gt;a2enmod&lt;/code&gt; command as shown above.&lt;/p&gt;

&lt;h1&gt;
  
  
  Install Git
&lt;/h1&gt;

&lt;p&gt;We will clone the GitHub repo that has the Laravel application and so we need to ensure that we have Git installed in our server. If Git isn't installed we will install it.&lt;/p&gt;

&lt;p&gt;I'd use an if statement for this logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check if git is installed, if it isn't, install it.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; git &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"installing git ================================================================="&lt;/span&gt;
  &lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
  &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; git
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Git Installation complete ======================================================="&lt;/span&gt;
  &lt;span class="nb"&gt;echo
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Install Composer
&lt;/h1&gt;

&lt;p&gt;Composer is a PHP dependency manager that facilitates the download of PHP libraries in our projects. Composer both works great with and makes it much easier to install Laravel.&lt;/p&gt;

&lt;p&gt;I will first download the Composer installer script from the composer site using &lt;code&gt;curl&lt;/code&gt;, pipe it directly to php, and execute it.&lt;/p&gt;

&lt;p&gt;Then move it into a location on my server's PATH so that it is globally accessible and lastly make it executable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install composer, composer is required to install laravel dependencies&lt;/span&gt;
&lt;span class="c"&gt;# Use curl to download the Composer installer script and pipe it directly to php for execution.&lt;/span&gt;
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; https://getcomposer.org/installer | php
&lt;span class="c"&gt;# Move the script to a location in your path so that it is executable globally&lt;/span&gt;
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;composer.phar /usr/local/bin/composer
&lt;span class="c"&gt;# Make composer executable&lt;/span&gt;
&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/bin/composer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Clone the Repository and Install Dependencies
&lt;/h1&gt;

&lt;p&gt;We will clone &lt;a href="https://github.com/laravel/laravel" rel="noopener noreferrer"&gt;this repo&lt;/a&gt;, this is the official Laravel application repo.&lt;/p&gt;

&lt;p&gt;Before cloning the repo I want to delete any content that was in that directory, we will be cloning directly into the &lt;code&gt;/var/www/html&lt;/code&gt; directory. You could create a new directory for this but I want to keep this whole process as simple as possible.&lt;/p&gt;

&lt;p&gt;Now I navigate into the &lt;code&gt;/var/www/html&lt;/code&gt; directory and then I'll go ahead and clone the repo, I will do this in Apache document root and then install the dependencies with composer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before cloning the git repository and installing it's dependencies with composer first remove the content in the /var/www/html directory so we can clone directly into it&lt;/span&gt;
&lt;span class="nb"&gt;echo
sudo rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/www/html/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚡ &lt;strong&gt;SIDE NOTE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I was running into a lot of permission errors when root owned the files in the &lt;code&gt;/var/www/html&lt;/code&gt; directory and so I had to change the ownership so that my user would be the owner. Wherever you see &lt;code&gt;vagrant&lt;/code&gt; replace that with your username (the one you are using for this deployment).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add Vagrant user to the www-data group and correct file permissions&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-G&lt;/span&gt; www-data vagrant
&lt;span class="c"&gt;# Set the group ownership of the /var/www/html directory to www-data&lt;/span&gt;
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; vagrant:www-data /var/www/html
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Group ownership of /var/www/html changed ========================================================================"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;

&lt;span class="c"&gt;# Grant write permissions to the www-data group for the /var/www/html directory&lt;/span&gt;
&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 775 /var/www/html
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Group can now write to the /var/www/html directory =============================================================="&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;

&lt;span class="c"&gt;# Navigate to Apache Document root first&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /var/www/html
&lt;span class="c"&gt;# Clone the repo directory in this directory (remember the full stop at the end of the command, it is very important)&lt;/span&gt;
git clone https://github.com/laravel/laravel.git &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="c"&gt;# Install dependencies with composer&lt;/span&gt;
composer &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Update ENV File and Generate an Encryption Key
&lt;/h1&gt;

&lt;p&gt;We need to create a &lt;code&gt;.env&lt;/code&gt; file after cloning the git repository or starting a new Laravel project. The &lt;code&gt;.env.example&lt;/code&gt; file is typically copied, and the contents of the copied &lt;code&gt;.env&lt;/code&gt; file are then updated.&lt;/p&gt;

&lt;p&gt;The following commands are what we will use to copy the file from &lt;code&gt;.env.example&lt;/code&gt; to &lt;code&gt;.env&lt;/code&gt; and generate an encryption key. So add the below to your script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Update the env file and generate an encryption key&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /var/www/html
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
php artisan key:generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;php artisan key:generate&lt;/code&gt; command is typically used in Laravel projects to generate a new application key. This key is used for encryption and hashing within the Laravel application, such as encrypting session data and generating secure hashes.&lt;/p&gt;

&lt;p&gt;When you run &lt;code&gt;php artisan key:generate&lt;/code&gt;, Laravel generates a new random key and updates the APP_KEY value in the &lt;code&gt;.env&lt;/code&gt; file of your Laravel project with this new key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Change Permissions on Storage and Bootstrap Directory
&lt;/h3&gt;

&lt;p&gt;Honestly this step could have been carried out right before we create our &lt;code&gt;.env&lt;/code&gt; file and generate our APP_KEY, immediately after cloning the repo but whatever, it hurts no one.&lt;/p&gt;

&lt;p&gt;If your &lt;code&gt;/var/www/html/storage&lt;/code&gt; file and your &lt;code&gt;/var/www/html/bootstrap/cache&lt;/code&gt; files do not belong to the &lt;code&gt;www-data&lt;/code&gt; user you experience the error message below because the &lt;code&gt;www-data&lt;/code&gt; user which Apache uses for web related activities won't have the necessary permissions that Laravel needs to fully function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmhr791cfljb9zees1cke.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmhr791cfljb9zees1cke.png" alt="permission-denied-error" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the below to your script to change the ownership of those directories.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set permission for the storage and bootstrap directories&lt;/span&gt;
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; www-data /var/www/html/storage
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; www-data /var/www/html/bootstrap/cache
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Permission changed for the storage and bootstrap directories ===================================================="&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update ENV
&lt;/h3&gt;

&lt;p&gt;We also need to update the &lt;code&gt;.env&lt;/code&gt; file with our database credentials and update the &lt;code&gt;APP_URL&lt;/code&gt; value. Presently it points to the localhost as the APP_URL but I need it to point to my server's IP address so that I can access the Laravel application from my IP address and not just localhost (127.0.0.1).&lt;/p&gt;

&lt;p&gt;Like you might have already noticed I am trying to make this script as unattended as possible and so I will use &lt;code&gt;sed&lt;/code&gt; to search and replace the &lt;code&gt;APP_URL&lt;/code&gt; line in the &lt;code&gt;.env&lt;/code&gt; file with the actual value I want and then echo the other database credentials I want in the file.&lt;/p&gt;

&lt;p&gt;This will allow my updating the &lt;code&gt;.env&lt;/code&gt; file be as unattended as possible. Add the following to your script, ensure to replace with your own credentials where necessary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Change the APP_URL value to be the IP address of your server&lt;/span&gt;
&lt;span class="c"&gt;# Get the IP address of the machine&lt;/span&gt;
&lt;span class="nv"&gt;ip_address&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt; &lt;span class="nt"&gt;-I&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $2}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Update the value of APP_URL in the .env file&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"s/^APP_URL=.*/APP_URL=http:&lt;/span&gt;&lt;span class="se"&gt;\/\/&lt;/span&gt;&lt;span class="nv"&gt;$ip_address&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt; /var/www/html/.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hostname -I | awk '{print $2}'&lt;/code&gt; retrieves the IP address of the machine.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hostname -I&lt;/code&gt; gets a list of IP addresses associated with the hostname, and &lt;code&gt;awk '{print $2}'&lt;/code&gt; selects the second IP address from the list. (I prefer to use the second that comes up on my server as that is the unique one for me, you can change 2 to 1 as you see fit).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also need to add some database configuration parameters, we will use &lt;code&gt;sed&lt;/code&gt; to replace what needs replacing and echo the new items into the file.&lt;/p&gt;

&lt;p&gt;Add the following to your script&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add additional database config&lt;/span&gt;

&lt;span class="c"&gt;# Update the value of DB_CONNECTION to mysql&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"s/^DB_CONNECTION=.*/DB_CONNECTION=mysql/"&lt;/span&gt; /var/www/html/.env

&lt;span class="c"&gt;# Add additional database configuration parameters&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;DB_HOST=127.0.0.1&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;DB_PORT=3306&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;DB_DATABASE=laravel&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;DB_USERNAME=root&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;DB_PASSWORD=&lt;/span&gt;&lt;span class="se"&gt;\"\$&lt;/span&gt;&lt;span class="s2"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/www/html/.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Adjust the VirtualHost File
&lt;/h1&gt;

&lt;p&gt;Usually you will find your &lt;code&gt;index.html&lt;/code&gt; or &lt;code&gt;index.php&lt;/code&gt; file directly in the &lt;code&gt;/var/www/html&lt;/code&gt; directory but with laravel it is different, the &lt;code&gt;index.php&lt;/code&gt; file is located in the &lt;code&gt;public&lt;/code&gt; directory and so we have to tell apache to route the traffic into the public directory so it can find our home page there and serve that page by default.&lt;/p&gt;

&lt;p&gt;We just have to add &lt;code&gt;/public&lt;/code&gt; at the end of the DocumentRoot and at other places in the virtual host file where we have to define the document root of our project.&lt;/p&gt;

&lt;p&gt;Add the below to your script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;SERVER_ADMIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your email address&amp;gt;"&lt;/span&gt;
&lt;span class="nv"&gt;DOCUMENT_ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/www/html/public"&lt;/span&gt;

&lt;span class="c"&gt;# Update the 000-default.conf file&lt;/span&gt;
&lt;span class="nb"&gt;sudo sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"s/#?&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;*ServerName .*/ServerName &lt;/span&gt;&lt;span class="nv"&gt;$ip_address&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt; /etc/apache2/sites-available/000-default.conf
&lt;span class="nb"&gt;sudo sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"s/ServerAdmin .*/ServerAdmin &lt;/span&gt;&lt;span class="nv"&gt;$SERVER_ADMIN&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt; /etc/apache2/sites-available/000-default.conf
&lt;span class="nb"&gt;sudo sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"s|DocumentRoot .*|DocumentRoot &lt;/span&gt;&lt;span class="nv"&gt;$DOCUMENT_ROOT&lt;/span&gt;&lt;span class="s2"&gt;|"&lt;/span&gt; /etc/apache2/sites-available/000-default.conf

&lt;span class="c"&gt;# Restart Apache to apply changes&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart apache2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Run Database Migration
&lt;/h1&gt;

&lt;p&gt;The last step is to run your migrations to build your application’s database tables. This step is necessary if you don't want to get the error message below&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzw1jhcwggisratbkz2cj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzw1jhcwggisratbkz2cj.png" alt="db-error" width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ideally when you run the &lt;code&gt;migrate&lt;/code&gt; command, as we will next, your database gets created (if it previously didn't exist) along with the tables and necessary schema. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwq71fqjvhr2xo0krnn4n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwq71fqjvhr2xo0krnn4n.png" alt="cli-prompt" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note however that this command does not have a built-in &lt;code&gt;-y&lt;/code&gt; flag to automatically accept the database creation and so will require user input which is why I went back and added the creation of the database in the MySQL installation (you don't have to sweat it).&lt;/p&gt;

&lt;p&gt;Add the following to your script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run outstanding migrations&lt;/span&gt;
php artisan migrate
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Database migrated successfully =================================================================================="&lt;/span&gt;
&lt;span class="nb"&gt;echo
echo&lt;/span&gt; &lt;span class="s2"&gt;"LAMP stack deployment complete, Laravel Repo fully cloned and configured ========================================"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it for the bash script, save the changes and close the file&lt;/p&gt;

&lt;h1&gt;
  
  
  Make the Script Executable
&lt;/h1&gt;

&lt;p&gt;To be able to run the script we need to make it executable. Use the command below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x deploylamp.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Install Ansible
&lt;/h1&gt;

&lt;p&gt;The first part of this task was to automate our deployment with a bash script which we have done up until now, the second part is to run the aforementioned script using an Ansible playbook.&lt;/p&gt;

&lt;p&gt;To achieve this, we first need to install Ansible on our server, do this by running the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;software-properties-common
&lt;span class="nb"&gt;sudo &lt;/span&gt;add-apt-repository &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="nt"&gt;--update&lt;/span&gt; ppa:ansible/ansible
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;ansible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ &lt;strong&gt;EXPERT TIP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You need to have a common user amongst your servers so that Ansible can function properly. Go ahead and create matching users on all your servers and ensure that the users have sudo privilege and can run sudo commands without password. If you don't know how to, you can checkout my previous Ansible guide &lt;a href="https://dev.to/chigozieco/install-apache-web-server-and-serve-a-custom-webpage-using-ansible-3n73#zap-create-a-common-user"&gt;here&lt;/a&gt; to see how it's done.&lt;/p&gt;

&lt;p&gt;I will be using my vagrant user as I have one on all my servers.&lt;/p&gt;

&lt;p&gt;You also need to generate an ssh key on your master server (also called control node, this is the machine from which you will run your Ansible commands) and copy the public key to your slave server(s) (the managed node where you want the final result on). You can also find the steps in &lt;a href="https://dev.to/chigozieco/install-apache-web-server-and-serve-a-custom-webpage-using-ansible-3n73#zap-generate-ssh-keys-on-the-control-node"&gt;this post&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Create Inventory
&lt;/h1&gt;

&lt;p&gt;To use Ansible, all our slave servers (managed nodes) has to be listed in a file known as the inventory. You can name this file anything you choose and the file will contain the IP address or URL of the managed nodes.&lt;/p&gt;

&lt;p&gt;Create a file, name it anything you want, I will name mine &lt;code&gt;examhost&lt;/code&gt; as this project is for my exam and enter the IP address of your managed node(s).&lt;/p&gt;

&lt;p&gt;You can retrieve the IP address of your server by running the &lt;code&gt;ip a&lt;/code&gt; command on the server you want it's IP address.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vi examhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the IP addresses&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&amp;lt;machine IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save your file.&lt;/p&gt;

&lt;h1&gt;
  
  
  Configure Ansible Vault
&lt;/h1&gt;

&lt;p&gt;Ansible provides a built-in solution called Ansible Vault for encrypting sensitive data. We will create an encrypted file to store the MySQL root password as discussed here, and then decrypt it when needed during playbook execution.&lt;/p&gt;

&lt;p&gt;Using Ansible Vault allows you to encrypt sensitive data within Ansible playbooks, roles, or other files. First we will create the file with the below command.&lt;/p&gt;

&lt;p&gt;When you run the command you will be prompted for a password. After providing a password, the tool will launch whatever editor you have defined with $EDITOR, and defaults to vim if you haven't defined any. Once you are done with the editor session, the file will be saved as encrypted data.&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;EXPERT TIP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Take note of the directory you're at while creating the vault file, you will need to enter the file path in your play book. If you already missed it though, no worries you can use the command &lt;code&gt;find / -name [file name] 2&amp;gt;/dev/null&lt;/code&gt; to find the path&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-vault create mysql_pass.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpbkz5z294atj9xcsssu6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpbkz5z294atj9xcsssu6.png" alt="ansible-vault" width="664" height="87"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ensure you do not forget your password as you will need it when you run your playbook.&lt;/p&gt;

&lt;p&gt;When the file opens, enter the below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql_root_password: &amp;lt;YourMySQLRootPasswordHere&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To prove that the file has been encrypted, display the content using the &lt;code&gt;cat&lt;/code&gt; command and you will see something similar to the below image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw8wxxgxob2s711jtye0h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw8wxxgxob2s711jtye0h.png" alt="encrypted-vault-secret" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;TIP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If for any reason you need to edit an encrypted file in place, use the &lt;code&gt;ansible-vault edit&lt;/code&gt; command. This command will decrypt the file to a temporary file and allow you to edit the file, saving it back when done and removing the temporary file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-vault edit &amp;lt;file name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Configure Your Ansible Configuration File
&lt;/h1&gt;

&lt;p&gt;The default ansible configuration file is in the &lt;code&gt;ansible.cfg&lt;/code&gt; file however when you open that file you will find it almost empty with instructions on how to populate it.&lt;/p&gt;

&lt;p&gt;When you run the ansible --version you will see where your current config file is located.&lt;/p&gt;

&lt;p&gt;For what we want to do though we want our configuration very basic and so I will create a new &lt;code&gt;ansible.cfg&lt;/code&gt; file and populate it with the basic configuration I want to use for this project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vi ansible.cfg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enter the below into the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;defaults]
inventory &lt;span class="o"&gt;=&lt;/span&gt; examhost
remote_user &lt;span class="o"&gt;=&lt;/span&gt; vagrant
host_key_checking &lt;span class="o"&gt;=&lt;/span&gt; False

&lt;span class="o"&gt;[&lt;/span&gt;privilege_escalation]
become &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;become_method &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sudo
&lt;/span&gt;become_user &lt;span class="o"&gt;=&lt;/span&gt; root
become_ack_pass &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;ssh_connection]
ssh_args &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;ControlMaster&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;auto &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;ControlPersist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3600s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I first ran my playbook, the execution took a whole lot of time and so I added the &lt;code&gt;ssh_connection&lt;/code&gt; parameters to try and speed things up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check Connectivity
&lt;/h3&gt;

&lt;p&gt;You can check your connectivity to your server(s) using adhoc commands. So long as you correctly saved the public key of your ssh key in the authorized keys file of your slave node(s) and the slave node IP address in your inventory file is correct your ping should go through.&lt;/p&gt;

&lt;p&gt;Check your connection using the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible all &lt;span class="nt"&gt;-m&lt;/span&gt; ping
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The below image confirms that the connection was established successfully&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fute2kkmkwfxvjzgcowlp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fute2kkmkwfxvjzgcowlp.png" alt="successful-ping" width="630" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;EXPERT TIP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because we specified our inventory file in the ansible.cfg file and we are running the command from the same directory as where the ansible config file is saved, we do not need to pass the inventory file path along with the &lt;code&gt;-i&lt;/code&gt; flag with the adhoc command.&lt;/p&gt;

&lt;p&gt;Assuming you wanted to run the command from somewhere else this is the command to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible all &lt;span class="nt"&gt;-i&lt;/span&gt; &amp;lt;/path/to/inventory&amp;gt; &lt;span class="nt"&gt;-m&lt;/span&gt; ping
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Create Playbook
&lt;/h1&gt;

&lt;p&gt;We have finally gotten to the main event. An Ansible playbook is a YAML file that contains a set of instructions or tasks to be executed by Ansible on remote hosts. &lt;/p&gt;

&lt;p&gt;Playbooks allow you to define configurations, orchestrate multiple tasks, and automate complex deployments in a structured and repeatable way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vi deploylaravel.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the below to your playbook:&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="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Laravel app and create cron job&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;vars_files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/home/vagrant/ansible/mysql_pass.yml&lt;/span&gt;
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run bash script to deploy laravel app&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/vagrant/deploylamp.sh&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;mysql_root_password&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ensure the MAILTO variable is present so we can receive the cron job output&lt;/span&gt;
      &lt;span class="na"&gt;cronvar&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MAILTO&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cnma.devtest@gmail.com"&lt;/span&gt;
        &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vagrant"&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create a cron job to check the server’s uptime every 12 am&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check server uptime&lt;/span&gt;
        &lt;span class="na"&gt;minute&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0"&lt;/span&gt;
        &lt;span class="na"&gt;hour&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0"&lt;/span&gt;
        &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/uptime&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;awk&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;print&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;strftime(&lt;/span&gt;&lt;span class="se"&gt;\"\\&lt;/span&gt;&lt;span class="s"&gt;%Y-&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;%m-&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;%d&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;),&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/home/vagrant/uptime.log&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&amp;gt;&amp;amp;1"&lt;/span&gt;
        &lt;span class="c1"&gt;#"date &amp;amp;&amp;amp; /usr/bin/uptime &amp;amp;&amp;amp; echo &amp;gt;&amp;gt; /home/vagrant/uptime.log"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This playbook has 3 tasks, the first task will execute the script we wrote earlier.&lt;/p&gt;

&lt;p&gt;The second is optional to this project, it will set &lt;code&gt;MAILTO&lt;/code&gt; along with the email address. This simply tells cron to send an email with the output of the cron job to the mentioned email address when ever the job runs. Skip this task if you do not want to receive emails concerning the cron job. If you leave this however, you need to have already configured the ability to send email from your terminal. You can check out &lt;a href="https://dev.to/chigozieco/configure-postfix-to-send-email-with-gmails-smtp-from-the-terminal-4cco"&gt;this post&lt;/a&gt; to see how to achieve this.&lt;/p&gt;

&lt;p&gt;The last task will create a cron job that will run at every 12am and send the output to an &lt;code&gt;uptime.log&lt;/code&gt; file located in our user's home directory. If you took out the second task, then ensure to add the &lt;code&gt;user&lt;/code&gt; parameter as seen in the second task in your 3rd task if not it defaults to &lt;code&gt;root&lt;/code&gt; as the user creating the job.&lt;/p&gt;

&lt;h1&gt;
  
  
  Run Playbook
&lt;/h1&gt;

&lt;p&gt;Run the play book now with the below command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-playbook &lt;span class="nt"&gt;-i&lt;/span&gt; &amp;lt;path/to/inventory&amp;gt; &amp;lt;path/to/playbook&amp;gt; &lt;span class="nt"&gt;--ask-vault-pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--ask-vault-pass&lt;/code&gt; is necessary so that ansible will ask us for our vault password and it can use it to decrypt our secret (our mysql root password) and use it while running our script.&lt;/p&gt;

&lt;p&gt;For verbosity, to see the logs as the playbook is executed use the &lt;code&gt;-v&lt;/code&gt; flag. &lt;code&gt;-v&lt;/code&gt; being the lowest and &lt;code&gt;-vvvvv&lt;/code&gt; being the highest.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;NOTE&lt;/strong&gt;&lt;br&gt;
The playbook execution might take some time, so sit tight.&lt;/p&gt;

&lt;p&gt;What a successful playbook execution looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8n3mray30u5161f0axj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8n3mray30u5161f0axj.png" alt="successful-run" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can confirm that the cronjob was successfully set by listing the crontab for that user, cronjobs are user specific so ensure you run the command for the user you used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crontab &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feuh2ti95qm6m6xwoaoy5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feuh2ti95qm6m6xwoaoy5.png" alt="cronjob" width="800" height="92"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Laravel App Homepage
&lt;/h1&gt;

&lt;p&gt;After successfully going through this process you should see either of these displayed when you enter your IP address in your browser.&lt;/p&gt;

&lt;p&gt;They are both the same page just in light mode and dark mode. This was a very tumultuous ride. Take note of my IP address in the images.&lt;/p&gt;

&lt;h3&gt;
  
  
  Light mode
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03xc1iv4laqc0jn45jsu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03xc1iv4laqc0jn45jsu.png" alt="light-mode-laravel" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dark mode
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnus3jzyo6gfu7u58r8ti.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnus3jzyo6gfu7u58r8ti.png" alt="dark-mode-laravel" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Link to a copy of the &lt;a href="https://github.com/ChigozieCO/laravel-application-deployment/blob/main/uptime.log" rel="noopener noreferrer"&gt;uptime.log file&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And that's it. Exam done and dusted!!!&lt;/p&gt;

</description>
      <category>linux</category>
      <category>mysql</category>
      <category>php</category>
      <category>bash</category>
    </item>
    <item>
      <title>Install Apache Web Server and Serve a Custom Webpage Using Ansible</title>
      <dc:creator>ChigozieCO</dc:creator>
      <pubDate>Mon, 29 Apr 2024 13:21:20 +0000</pubDate>
      <link>https://forem.com/chigozieco/install-apache-web-server-and-serve-a-custom-webpage-using-ansible-3n73</link>
      <guid>https://forem.com/chigozieco/install-apache-web-server-and-serve-a-custom-webpage-using-ansible-3n73</guid>
      <description>&lt;p&gt;As a cloud engineer, automation should be at the center of everything you do. Imagine you had to setup two servers, install some webservers and add some programs for a new staff, easy peasy to do on the two servers. Just log into them one at a time and install what needs to be installed and hand over to the new staff.&lt;/p&gt;

&lt;p&gt;Now flip it and you have to setup 100 or 200 servers for 3 departments, it would take you weeks to do this manually and you are prone to making mistakes cos the repetition would become very tiring. However when you automate the process it will take you less than an hour to complete.&lt;/p&gt;

&lt;h1&gt;
  
  
  ⚡ What is Ansible
&lt;/h1&gt;

&lt;p&gt;Ansible is an open-source platform used for automation and for various operations such as configuration management, application deployment, task automation, and IT orchestration. Ansible is easy to set up, and it is efficient, reliable, and powerful.&lt;/p&gt;

&lt;h1&gt;
  
  
  ⚡ Ansible Architecture
&lt;/h1&gt;

&lt;p&gt;Ansible uses the concepts of control and managed nodes. It connects from the control node, any machine with Ansible installed, to the managed nodes sending commands and instructions to them.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚡ Control node requirements
&lt;/h3&gt;

&lt;p&gt;For your control node (the machine that runs Ansible), you can use nearly any UNIX-like machine with Python installed. This includes Red Hat, Debian, Ubuntu, macOS, BSDs, and Windows under a Windows Subsystem for Linux (WSL) distribution. Windows without WSL is not natively supported as a control node.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚡ Managed node requirements
&lt;/h3&gt;

&lt;p&gt;The managed node (the machine that Ansible is managing) does not require Ansible to be installed, but requires Python to run Ansible-generated Python code. The managed node also needs a user account that can connect through SSH to the node with an interactive POSIX shell.&lt;/p&gt;

&lt;h1&gt;
  
  
  ⚡ Install Ansible on Ubuntu
&lt;/h1&gt;

&lt;p&gt;To install Ansible on Ubuntu you need to add the PPA to you apt repository and also install the necessary dependencies. Use the below commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;software-properties-common
&lt;span class="nb"&gt;sudo &lt;/span&gt;add-apt-repository &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="nt"&gt;--update&lt;/span&gt; ppa:ansible/ansible
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;ansible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  ⚡ Create a Common User
&lt;/h1&gt;

&lt;p&gt;Ansible requires a common user across all the nodes, if the user you are working with is called ansible ensure you have an ansible user on all your nodes.&lt;/p&gt;

&lt;p&gt;The user on my control node is named vagrant and so I will create a vagrant user (or any other user you want) with sudo privilege on my other nodes and ensure that the user can run sudo commands without password.&lt;/p&gt;

&lt;p&gt;(I already have a vagrant user on all my servers and so I will be using the vagrant user for this walkthrough).&lt;/p&gt;

&lt;p&gt;This is useful and necessary so that when we run the commands and SSH into our nodes, we won't be stuck because any of the nodes requires a password.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;adduser vagrant
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To give this user sudo privilege and ensure that the user can run sudo commands without password, I will edit the &lt;code&gt;/etc/sudoers&lt;/code&gt; file using the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;visudo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enter the below into the file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant &lt;span class="nv"&gt;ALL&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;ALL&lt;span class="o"&gt;)&lt;/span&gt; NOPASSWD:ALL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do this for all the servers.&lt;/p&gt;

&lt;h1&gt;
  
  
  ⚡ Generate SSH Keys on the Control Node
&lt;/h1&gt;

&lt;p&gt;Before you begin this step switch to the Vagrant user.&lt;/p&gt;

&lt;p&gt;Generate an SSH key on the control node. The private key will be on the master and we will copy the public key to the managed nodes (hosts).&lt;/p&gt;

&lt;p&gt;Use the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;su vagrant
ssh-keygen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we will copy the public key to the managed nodes, retrieve the public key and then navigate to the &lt;code&gt;.ssh/authorized_keys&lt;/code&gt;/ file and add the copied key into the file:&lt;/p&gt;

&lt;h4&gt;
  
  
  On the control node
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; .ssh/id_rsa.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the outputted public key &lt;/p&gt;

&lt;h4&gt;
  
  
  On the managed node
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vi authorized_keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  ⚡ Create Your Inventory
&lt;/h1&gt;

&lt;p&gt;Ansible inventory is a file containing a list of your managed host, these are the servers on which your tasks will be carried out.&lt;/p&gt;

&lt;p&gt;You can make use of the default hosts files in the /etc/ansible directory but it's better to create yours and use that so that in a case where you make a mistake you can use the default host file provided by ansible as a reference for the correct configuration syntax.&lt;/p&gt;

&lt;p&gt;To that effect I will make an ansible directory and this is here i will be saving my inventory and playbooks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;ansible
&lt;span class="nb"&gt;sudo &lt;/span&gt;vi myhosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the inventory file add the IP addresses of your managed nodes. You can use the &lt;code&gt;IP a&lt;/code&gt; command to get the IP address of the nodes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvfcboi2rib3e0kq16v1x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvfcboi2rib3e0kq16v1x.png" alt="Inventory" width="588" height="89"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  ⚡ Configure your Ansible Configuration file
&lt;/h1&gt;

&lt;p&gt;The default ansible configuration file is in the &lt;code&gt;ansible.cfg&lt;/code&gt; file however when you open that file you will find it almost empty with instructions on how to populate it.&lt;/p&gt;

&lt;p&gt;When you run the &lt;code&gt;ansible --version&lt;/code&gt; you will see where your current config file is located.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3udzw47qgbu24lj1xkg2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3udzw47qgbu24lj1xkg2.png" alt="Ansible config" width="800" height="149"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For what we want to do in this post though we want our configuration very basic and so I will create a new &lt;code&gt;ansible.cfg&lt;/code&gt; file in the ansible directory I created earlier and populate it with the basic configuration I want to use for this post.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vi ansible.cfg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enter the below into the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;defaults]
inventory &lt;span class="o"&gt;=&lt;/span&gt; myhosts //&lt;span class="o"&gt;(&lt;/span&gt;the config file is &lt;span class="k"&gt;in &lt;/span&gt;the same place as the inventory, hence why I don&lt;span class="s1"&gt;'t need to add the full path to the inventory)
remote_user = ansible //(or whatever user you are using)
host_key_checking = false

[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ack_pass = false //(we don'&lt;/span&gt;t want a password&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file. now if you run the &lt;code&gt;ansible --version&lt;/code&gt; command again it will show the new config file path.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ft707i8ga6v83wxey8w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ft707i8ga6v83wxey8w.png" alt="ansible.cfg" width="800" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  ⚡ Check Connectivity using adhoc Command
&lt;/h1&gt;

&lt;p&gt;Using ad hoc commands is a quick way to run a single task on one or more managed nodes. &lt;/p&gt;

&lt;p&gt;Some examples of valid use cases are rebooting servers, copying files, checking connection status, managing packages, gathering facts, etc.&lt;/p&gt;

&lt;p&gt;The pattern for ad hoc commands looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible &lt;span class="o"&gt;[&lt;/span&gt;host-pattern] &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;module] &lt;span class="nt"&gt;-a&lt;/span&gt; “[module options]”
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will use the ping module to check the connectivity to our servers.&lt;/p&gt;

&lt;p&gt;Run the below command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible all &lt;span class="nt"&gt;-m&lt;/span&gt; ping
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 &lt;strong&gt;TIP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because we specified our inventory file in the &lt;code&gt;ansible.cfg&lt;/code&gt; file we do not need to pass the inventory file path along with the &lt;code&gt;-i&lt;/code&gt; flag with the adhoc command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj78k7cnlc8t9p4q24bf2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj78k7cnlc8t9p4q24bf2.png" alt="Ping result" width="639" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  ⚡ Prepare Custom Website Files
&lt;/h1&gt;

&lt;p&gt;Apache is a webserver and it serves an apache website by default, this is not the site we want our apache webserver to server and so we would need to retrieve the config file for our custom website.&lt;/p&gt;

&lt;p&gt;The files are hosted in my GitHub account and so clone that into a folder on my control node.&lt;/p&gt;

&lt;p&gt;First I need to install git on the control node, do that using the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I will create a new directory and it is in this directory that I would clone &lt;a href="https://github.com/ChigozieCO/assignment-03-WP-Pusher" rel="noopener noreferrer"&gt;this repo&lt;/a&gt; into (you can use any repo of your choice). The repo contains the html and CSS code for a webpage I designed earlier on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;webpage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I'll navigate into the directory and clone the project there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;webpage
git clone https://github.com/ChigozieCO/assignment-03-WP-Pusher.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This page will be used in our playbook.&lt;/p&gt;

&lt;h1&gt;
  
  
  ⚡ Create Playbook
&lt;/h1&gt;

&lt;p&gt;Create a playbook directory and navigate into the directory&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;playbook
&lt;span class="nb"&gt;cd &lt;/span&gt;playbook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we will create our playbook, I will name it serverplay as the playbook will be contain tasks to install apache2 web server on my nodes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vi serverplay.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the below and paste it into your playbook, this is the content of your playbook&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;---&lt;/span&gt;
- name: Setup webserver and copy custom webpage
  hosts: all
  become: &lt;span class="nb"&gt;yes
  &lt;/span&gt;tasks:
    - name: Update apt package cache &lt;span class="o"&gt;(&lt;/span&gt;Ubuntu&lt;span class="o"&gt;)&lt;/span&gt; / Update yum package cache &lt;span class="o"&gt;(&lt;/span&gt;CentOS&lt;span class="o"&gt;)&lt;/span&gt;
      when: ansible_os_family &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'Debian'&lt;/span&gt;
      apt:
        update_cache: &lt;span class="nb"&gt;yes&lt;/span&gt;

    - name: Update yum package cache &lt;span class="o"&gt;(&lt;/span&gt;CentOS&lt;span class="o"&gt;)&lt;/span&gt; / Update apt package cache &lt;span class="o"&gt;(&lt;/span&gt;Ubuntu&lt;span class="o"&gt;)&lt;/span&gt;
      when: ansible_os_family &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'RedHat'&lt;/span&gt;
      yum:
        update_cache: &lt;span class="nb"&gt;yes&lt;/span&gt;

    - name: Install Apache &lt;span class="o"&gt;(&lt;/span&gt;Ubuntu&lt;span class="o"&gt;)&lt;/span&gt; / Install Httpd &lt;span class="o"&gt;(&lt;/span&gt;CentOS&lt;span class="o"&gt;)&lt;/span&gt;
      package:
        name: &lt;span class="s2"&gt;"{{ 'apache2' if ansible_os_family == 'Debian' else 'httpd' }}"&lt;/span&gt;
        state: latest

    - name: Enable and start Apache/Httpd
      ansible.builtin.service:
        name: &lt;span class="s2"&gt;"{{ 'apache2' if ansible_os_family == 'Debian' else 'httpd' }}"&lt;/span&gt;
        enabled: &lt;span class="nb"&gt;yes
        &lt;/span&gt;state: started

    - name: Copy custom webpage files
      ansible.builtin.copy:
        src: &lt;span class="s2"&gt;"~/ansible/webpage/assignment-03-WP-Pusher/"&lt;/span&gt;
        dest: &lt;span class="s2"&gt;"/var/www/html/"&lt;/span&gt;
        mode: &lt;span class="s1"&gt;'0755'&lt;/span&gt;

    - name: Restart Apache2 &lt;span class="o"&gt;(&lt;/span&gt;Ubuntu&lt;span class="o"&gt;)&lt;/span&gt; / Httpd &lt;span class="o"&gt;(&lt;/span&gt;CentOS&lt;span class="o"&gt;)&lt;/span&gt;
      ansible.builtin.service:
        name: &lt;span class="s2"&gt;"{{ 'apache2' if ansible_os_family == 'Debian' else 'httpd' }}"&lt;/span&gt;
        state: restarted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  ⚡ Run Playbook
&lt;/h1&gt;

&lt;p&gt;Run the play book now with the below command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-playbook playbook/serverplay.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 Note&lt;/p&gt;

&lt;p&gt;Because  I am running my playbook from the ansible directory we created earlier, I do not need to specify the location of my inventory as I have done that in my config file already.&lt;/p&gt;

&lt;p&gt;My playbook ran successfully and we can see it in the screenshot below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzc2mjk8upb8ehptgbrvc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzc2mjk8upb8ehptgbrvc.png" alt="output of executed playbook" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To see your webpage enter your node's IP address in a web browser, from the below screen shots you can see the pages displayed on my browser.&lt;/p&gt;

&lt;h4&gt;
  
  
  managed node 1
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp6v4wqi4v0wgee0mfnko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp6v4wqi4v0wgee0mfnko.png" alt="centos" width="800" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  managed node 2
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnel5sbiarju6za2pzk79.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnel5sbiarju6za2pzk79.png" alt="ubuntu" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If this post has helped you, consider supporting me: &lt;a href="https://buymeacoffee.com/chigozieco" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.buymeacoffee.com%2Fassets%2Fimg%2Fcustom_images%2Fyellow_img.png" alt="Buy Me A Coffee" width="170" height="37"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>apache</category>
      <category>devops</category>
      <category>automation</category>
    </item>
    <item>
      <title>Configure Postfix to Send Email with Gmail's SMTP From the Terminal</title>
      <dc:creator>ChigozieCO</dc:creator>
      <pubDate>Wed, 10 Apr 2024 13:15:51 +0000</pubDate>
      <link>https://forem.com/chigozieco/configure-postfix-to-send-email-with-gmails-smtp-from-the-terminal-4cco</link>
      <guid>https://forem.com/chigozieco/configure-postfix-to-send-email-with-gmails-smtp-from-the-terminal-4cco</guid>
      <description>&lt;p&gt;Open a tab on your browser, head to Gmail or yahoo mail or whatever other mail agent you use and then compose an email. This is the normal method of sending email from our computer that almost everyone is familiar with, but what if I told you you can send emails directly from your Linux terminal?&lt;/p&gt;

&lt;p&gt;There are various ways to go about this but I will be using Mailx and Postfix on an ubuntu OS. Follow along and in no time you would have successfully sent your first mail from your Linux terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Mailx and Postfix
&lt;/h2&gt;

&lt;p&gt;Mailx is a UNIX utility program for sending and receiving mail, also known as a Mail User Agent program.&lt;/p&gt;

&lt;p&gt;The mailx command in Linux is a powerful tool that lets you send emails directly from your command line. It’s simple, efficient, and incredibly flexible.&lt;/p&gt;

&lt;p&gt;It needs to be installed&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fresce5eld8e4i9bsdeso.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fresce5eld8e4i9bsdeso.png" alt="mailx not found" width="606" height="111"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First update your apt package&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use this command to install mailx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;mailutils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Email is sent and received using Postfix, a mail transfer agent (MTA). When you install mailx with the above command postfix is automatically installed as well if you didn't already have it installed.&lt;/p&gt;

&lt;p&gt;After successful installation you will see the postfix configuration dialog (as shown below), you can choose the &lt;code&gt;internet site&lt;/code&gt; option highlighted below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30fr3sfjs80v4hvna7rn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30fr3sfjs80v4hvna7rn.png" alt="postfix config" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Like I said, installing mailx will install postfix automatically however if you want to install postfix manually, use the below command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;postfix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Generate an Application Specific Password
&lt;/h2&gt;

&lt;p&gt;We will need to save our gmail password in a file for this configuration but for security purposes I do not want to save my password in a file and this is why we will be generating an app specific password that we will use for this purpose.&lt;/p&gt;

&lt;p&gt;This password will be unique and can only be used by postfix.&lt;/p&gt;

&lt;p&gt;This password will be used by Postfix for SMTP authentication instead of your actual Gmail password. Even if someone were to get hold of this password it wouldn't work if it weren't from postfix. &lt;/p&gt;

&lt;p&gt;Follow these steps to generate the password:&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;TIP&lt;/strong&gt;&lt;br&gt;
You must enable two factor authentication to be able to generate an app specific password on gmail.&lt;/p&gt;

&lt;p&gt;⚡ Go to your Google Account settings (&lt;a href="https://myaccount.google.com/" rel="noopener noreferrer"&gt;https://myaccount.google.com/&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;⚡ Navigate to the "Security" section.&lt;/p&gt;

&lt;p&gt;⚡ Under "Signing in to Google", select 2-Step Verification.&lt;/p&gt;

&lt;p&gt;⚡ At the bottom of the page, select App passwords&lt;/p&gt;

&lt;p&gt;⚡ Type a name for the password.&lt;/p&gt;

&lt;p&gt;⚡ Click on "Create".&lt;/p&gt;

&lt;p&gt;⚡ Copy the generated application-specific password and keep it handy, we will make use of it. &lt;/p&gt;

&lt;p&gt;This password will be used in the Postfix configuration.&lt;/p&gt;
&lt;h2&gt;
  
  
  Create Your &lt;code&gt;sasl_passwd&lt;/code&gt; File
&lt;/h2&gt;

&lt;p&gt;sasl is simple authentication and security layer.&lt;/p&gt;

&lt;p&gt;We will create a &lt;code&gt;sasl_passwd&lt;/code&gt; file in the &lt;code&gt;/etc/postfix/sasl&lt;/code&gt; directory&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vi /etc/postfix/sasl/sasl_passwd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file we will add the app specific password that we generated above.&lt;/p&gt;

&lt;p&gt;Enter the below code into the file, what you're basically doing here is saving your gmail authentication (username and password) so that it can be used by postfix for authenticating.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Expert Tip&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The regular port used for postfix is 587, I started out using port 587 but it turned out that for some reasons my port 587 was closed (either that or my ISP was blocking communication from that port) and so my mails weren't sending so I had to switch to port 465 as you will see me use in this walkthrough.&lt;/p&gt;

&lt;p&gt;In the same light, if you follow through this post and your mails do not make it out of the mail queue, try other posts such as 587 and 995.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Enter your password in the format below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;smtp.gmail.com]:465 &amp;lt;your gmail email address&amp;gt;:&amp;lt;the app specific password&amp;gt;

eg &lt;span class="o"&gt;[&lt;/span&gt;smtp.gmail.com]:465 example@abc.com:yourAPPspecificpassword 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ &lt;strong&gt;NOTE&lt;/strong&gt;&lt;br&gt;
This is the email address that will be sender for all your outgoing emails.&lt;/p&gt;

&lt;p&gt;The next thing to do is to create a hash database file for the password because postfix will only read the hash.&lt;/p&gt;

&lt;p&gt;Do this with the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;postmap /etc/postfix/sasl/sasl_passwd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a &lt;code&gt;sasl_passwd.db&lt;/code&gt; file in the &lt;code&gt;/etc/postfix/sasl&lt;/code&gt; directory. You can &lt;code&gt;ls&lt;/code&gt; that directory to confirm that it was correctly created.&lt;/p&gt;

&lt;p&gt;At this point, for security purposes we will change the permissions of these two files above to ensure that only root (which is the owner of the files) can read them.&lt;/p&gt;

&lt;p&gt;Use the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /etc/postfix/sasl/sasl_passwd /etc/postfix/sasl/sasl_passwd.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure Postfix to Use the Gmail Server
&lt;/h2&gt;

&lt;p&gt;If you have your own custom smtp server you would be using it here but we will use the Gmail smtp server instead.&lt;/p&gt;

&lt;p&gt;Open the postfix &lt;code&gt;main.cf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vi /etc/postfix/main.cf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Locate the &lt;code&gt;relayhost&lt;/code&gt; and enter Gmail smtp there&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;relayhost &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;smtp.gmail.com]:465
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to the end of &lt;code&gt;/etc/postfix/main.cf&lt;/code&gt; to enable SASL authentication for Postfix.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Enable SASL authentication&lt;/span&gt;
smtp_use_tls &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;yes
&lt;/span&gt;smtp_sasl_auth_enable &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;yes
&lt;/span&gt;smtp_sasl_security_options &lt;span class="o"&gt;=&lt;/span&gt; noanonymous
smtp_sasl_password_maps &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;hash&lt;/span&gt;:/etc/postfix/sasl/sasl_passwd
smtp_tls_security_level &lt;span class="o"&gt;=&lt;/span&gt; encrypt
smtp_tls_CAfile &lt;span class="o"&gt;=&lt;/span&gt; /etc/ssl/certs/ca-certificates.crt
smtp_tls_wrappermode &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm that the certificate file exists. If it doesn't, you can install it using the below command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;ca-certificates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Restart Postfix
&lt;/h2&gt;

&lt;p&gt;To ensure that all these changes take effect, we need to restart Postfix.&lt;/p&gt;

&lt;p&gt;Do this with the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart postfix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Firewall
&lt;/h2&gt;

&lt;p&gt;Depending on your firewall configurations your firewall could block communications and cause the emails to not be sent so check your firewall status and if need be add rules to ensure that smtp is allowed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your firewall is inactive you can proceed along without adding any new rules.&lt;/p&gt;

&lt;p&gt;To add new rules to allow smtp, use the below commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow &lt;span class="s2"&gt;"Postfix"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow &lt;span class="s2"&gt;"postfix SMTP"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow &lt;span class="s2"&gt;"Postfix Submission"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqytcwnhdkfb4b9sokb10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqytcwnhdkfb4b9sokb10.png" alt="postfix firewall rule" width="588" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Export SSL Cert File
&lt;/h2&gt;

&lt;p&gt;This step is optional, you might have no need for it. &lt;/p&gt;

&lt;p&gt;I needed to do this because I discovered that my openssl in my env could not find the path to my SSL certificate and so my connections kept dropping before the TLS handshake could be completed.&lt;/p&gt;

&lt;p&gt;To solve this issue and successfully send a mail from my terminal, I have to export my CAFile path so openssl knows where to look.&lt;/p&gt;

&lt;p&gt;Use the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SSL_CERT_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/ssl/certs/ca-certificates.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To persist it so that every time you boot up your system the changes remain, enter the above command in the &lt;code&gt;~/.bashrc&lt;/code&gt; file or the &lt;code&gt;~/.bash_profile&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vi ~/.bashrc
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SSL_CERT_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/ssl/certs/ca-certificates.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To apply the changes immediately, you can close your terminal and open another one or use the below command to apply the change to that same terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Send Mail
&lt;/h2&gt;

&lt;p&gt;There are various ways you can send a mail from your terminal, I will show a few of them below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method 1
&lt;/h3&gt;

&lt;p&gt;The first method is using the echo command with mailx.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Message 1 sent through the terminal"&lt;/span&gt; | mailx &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"Echo command mail 1"&lt;/span&gt; &amp;lt;email address of recipient&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctpi8sl4c0hk61cifinm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctpi8sl4c0hk61cifinm.png" alt="terminal mail1" width="800" height="71"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We do not need to specify the sender as we have already done that in our &lt;code&gt;sasl_passwd&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;s&lt;/code&gt; flag indicates the subject of the mail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6uxxzssk2vgugreriz5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6uxxzssk2vgugreriz5.png" alt="sent mail1" width="748" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Method 2
&lt;/h3&gt;

&lt;p&gt;In an instance when you want to write an email with a very full body containing multiple lines you can write the body directly in the terminal without the echo command.&lt;/p&gt;

&lt;p&gt;Start with the &lt;code&gt;mailx&lt;/code&gt; command, the subject and the recipient then click enter. At this point mailx will wait for you to type the body of your email.&lt;/p&gt;

&lt;p&gt;Go ahead and write the body of your email.&lt;/p&gt;

&lt;p&gt;When you are done, enter a new line and press &lt;code&gt;ctrl D&lt;/code&gt; to signify the end of the email and send it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mailx &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"Terminal email number 2"&lt;/span&gt; &amp;lt;recipient&lt;span class="s1"&gt;'s email address&amp;gt;
This is a mail trying out another method of sending an email from the terminal.
This method we are typing multiple times.
You can type multiple lines with the echo command as well.
But when you want multiple lines I prefer to use this method.
That'&lt;/span&gt;s it &lt;span class="k"&gt;for &lt;/span&gt;this example.

Thanks guys.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3mlnwf6pmup94qio51gl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3mlnwf6pmup94qio51gl.png" alt="terminal mail2" width="747" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see that the message was received.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3wp827j24gfib49stuwt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3wp827j24gfib49stuwt.png" alt="sent mail2" width="648" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Method 3
&lt;/h3&gt;

&lt;p&gt;It is equally possible for the body of your mail to be gotten from the contents of a file.&lt;/p&gt;

&lt;p&gt;To do this you will redirect the contents of the file to the command to send the mail like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mailx &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"Mail body from a file, mail 3"&lt;/span&gt; firstperson@mail.com, person2@mail.com, person3@mail.com &amp;lt; path/to/file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also send the mail to several people at the same time as shown in the code snippet above.&lt;/p&gt;

&lt;p&gt;To demonstrate this, the first thing I will do is to create the file and then add some text to it. I'll do this with one command. Shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"This is the body of our mail that will be gotten from this file mail.txt &amp;gt;&amp;gt; mail.txt
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgp9qh9dnwyh8hd1uh8uu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgp9qh9dnwyh8hd1uh8uu.png" alt="mail text" width="800" height="108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next I will send my mail&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mailx &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"Mail body from a file, mail 3"&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;recipient email address] &amp;lt; ./mail.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bq4h6mmfpca5r1x9er7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bq4h6mmfpca5r1x9er7.png" alt="terminal mail3" width="800" height="84"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see that the content of the file has now been translated into the body of our mail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fowseo0i293o2pjhvjers.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fowseo0i293o2pjhvjers.png" alt="sent mail3" width="739" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful mailx flags
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Subject of the mail&lt;/td&gt;
&lt;td&gt;echo 'Hello' | mailx -s 'Subject' &lt;a href="mailto:user@mail.com"&gt;user@mail.com&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add a CC to the mail, this will add the email address specified after the flag as CC (carbon copy) to the mail&lt;/td&gt;
&lt;td&gt;echo 'Hello' | mailx -s 'Subject' -c &lt;a href="mailto:user2@mail.com"&gt;user2@mail.com&lt;/a&gt; &lt;a href="mailto:user@mail.com"&gt;user@mail.com&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add a BCC to the mail, this will add the email address specified after the flag as BCC (blind carbon copy) to the mail&lt;/td&gt;
&lt;td&gt;echo 'Hello' | mailx -s 'Subject' -b &lt;a href="mailto:user2@mail.com"&gt;user2@mail.com&lt;/a&gt; &lt;a href="mailto:user@mail.com"&gt;user@mail.com&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-r&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Specify recipient email address to the mail&lt;/td&gt;
&lt;td&gt;echo 'Hello' | mailx -r &lt;a href="mailto:from@mail.com"&gt;from@mail.com&lt;/a&gt; -s 'Subject' &lt;a href="mailto:user@mail.com"&gt;user@mail.com&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add attachment to the mail&lt;/td&gt;
&lt;td&gt;echo 'Hello' | mailx -s   'Subject' -a file.txt &lt;a href="mailto:user@mail.com"&gt;user@mail.com&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And there you have it, don't forget to share this post if you found it helpful.&lt;/p&gt;

&lt;p&gt;If this post has helped you, consider supporting me: &lt;a href="https://buymeacoffee.com/chigozieco" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.buymeacoffee.com%2Fassets%2Fimg%2Fcustom_images%2Fyellow_img.png" alt="Buy Me A Coffee" width="170" height="37"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ubuntu</category>
      <category>postfix</category>
      <category>smtp</category>
      <category>devops</category>
    </item>
    <item>
      <title>Vagrant CLI Commands Cheat Sheet</title>
      <dc:creator>ChigozieCO</dc:creator>
      <pubDate>Mon, 11 Mar 2024 10:24:35 +0000</pubDate>
      <link>https://forem.com/chigozieco/vagrant-cli-commands-cheat-sheet-f7c</link>
      <guid>https://forem.com/chigozieco/vagrant-cli-commands-cheat-sheet-f7c</guid>
      <description>&lt;p&gt;In a &lt;a href="https://dev.to/chigozieco/install-virtual-machine-with-vagrant-remotely-access-it-4k5j"&gt;previous post&lt;/a&gt;, I explained what Vagrant is, how to install it, how to use it to install virtual machines and how to connect to installed VM using it. If you haven't already seen that post you can check it out &lt;a href="https://dev.tolink%20again"&gt;here&lt;/a&gt;, check it out first and head back to this post to familiarize yourself with these commands.&lt;/p&gt;

&lt;p&gt;If you find this post useful, consider supporting me: &lt;a href="https://buymeacoffee.com/chigozieco" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.buymeacoffee.com%2Fassets%2Fimg%2Fcustom_images%2Fyellow_img.png" alt="Buy Me A Coffee" width="170" height="37"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 PRO TIP&lt;/strong&gt;&lt;br&gt;
When running Vagrant commands, you must be in a directory containing a Vagrantfile or you will receive the following error: &lt;code&gt;A Vagrant environment or target machine is required to run this command&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3psca0k8nu44u6l6c07g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3psca0k8nu44u6l6c07g.png" alt="non vagrant directory" width="700" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So be sure to navigate to the directory where you have your Vagrantfile saved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vagrant CLI Commands
&lt;/h2&gt;

&lt;p&gt;Below are most of the most basic vagrant commands you will likely use in your everyday interaction with vagrant. Nothing too deep or too complicated.&lt;/p&gt;




&lt;p&gt;📌 &lt;strong&gt;vagrant init &lt;/strong&gt;: This command initializes vagrant and generate a Vagrantfile with default configurations specifying the box you supplied in the command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyp0unnume8c45tqefcl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyp0unnume8c45tqefcl.png" alt="vagrant init" width="800" height="140"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;📌 &lt;strong&gt;vagrant init --minimal&lt;/strong&gt;: Generate a Vagrantfile without all the additional instructional comments that vagrant adds by default.&lt;/p&gt;




&lt;p&gt;📌 &lt;strong&gt;vagrant box add &lt;/strong&gt;:This will download a box image to your computer.&lt;/p&gt;




&lt;p&gt;📌 &lt;strong&gt;vagrant up&lt;/strong&gt;: Start up a virtual machine in vagrant. If this is your first time of running it, it will build the virtual machine using the configurations specified in your Vagrantfile. Subsequently, it just starts up your already created VM.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0szlss0usgqb999im8i8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0szlss0usgqb999im8i8.png" alt="vagrant up" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;📌 &lt;strong&gt;vagrant ssh&lt;/strong&gt;: Remotely access your vm in vagrant via ssh.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4dm4wuv706ko6wjf4qeo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4dm4wuv706ko6wjf4qeo.png" alt="vagrant ssh" width="800" height="562"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;📌 &lt;strong&gt;vagrant ssh-config&lt;/strong&gt;: Retrieve the IP address and port of your virtual machine (basically to view your ssh configuration just as the name states). This command will only work when your VM is running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fse8vehs25jsik5pxa96j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fse8vehs25jsik5pxa96j.png" alt="vagrant ssh-config error" width="699" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the command is successfully run, you should see something that resembles the below&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft51scza46k0m3jp8lffa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft51scza46k0m3jp8lffa.png" alt="vagrant ssh-config" width="800" height="203"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;📌 &lt;strong&gt;vagrant halt&lt;/strong&gt;: Halt the machine, this attempts a graceful shut down. Here the machine turns off just like when you shut down your physical machine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffms67pjteqrikago0h3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffms67pjteqrikago0h3.png" alt="vagrant halt" width="742" height="56"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;📌 &lt;strong&gt;vagrant suspend&lt;/strong&gt;: This command will save the state of your machine, just like when you hibernate your physical computer. A suspended machine is a machine that is turned off but not shut down.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F92odh5qtfy846e28xa1c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F92odh5qtfy846e28xa1c.png" alt="vagrant suspend" width="800" height="80"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;📌 &lt;strong&gt;vagrant resume&lt;/strong&gt;: This resumes a Vagrant managed machine that was previously suspended using &lt;code&gt;vagrant suspend&lt;/code&gt;. This command will not start up a machine that was powered off using &lt;code&gt;vagrant halt&lt;/code&gt;, if you attempt to start a shut down machine you will get the below error.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Featjb7vqmznqtk8vl08y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Featjb7vqmznqtk8vl08y.png" alt="vagrant resume error" width="719" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the command is successfully run, you should see something that resembles the below&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft5oco5gn4d1bm3mwozhv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft5oco5gn4d1bm3mwozhv.png" alt="vagrant resume" width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;📌 &lt;strong&gt;vagrant reload&lt;/strong&gt;: This command is very useful when modifications have been made to the Vagrantfile. It is the equivalent of running the &lt;code&gt;vagrant halt&lt;/code&gt; and &lt;code&gt;vagrant up&lt;/code&gt; command. It is required for changes made in the Vagrantfile to take effect.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufn0lm5dnqamkgea4xcq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufn0lm5dnqamkgea4xcq.png" alt="vagrant reload" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;📌 &lt;strong&gt;vagrant status&lt;/strong&gt;: This will tell you the state of the machines Vagrant is managing. It comes in very handy when you forget whether you VMs are still running, halted or suspended, trust me it's very easy to forget especially when you start managing a lot of virtual machines with vagrant.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3opwheum0h84spi98hi8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3opwheum0h84spi98hi8.png" alt="vagrant status" width="639" height="135"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;📌 &lt;strong&gt;vagrant destroy&lt;/strong&gt;: stops and deletes all the virtual machine created with vagrant and also destroys all resources that were created during the machine creation process. &lt;/p&gt;

</description>
      <category>vagrant</category>
      <category>linux</category>
      <category>cloud</category>
      <category>cli</category>
    </item>
    <item>
      <title>Install Virtual Machine With Vagrant &amp; Remotely Access it</title>
      <dc:creator>ChigozieCO</dc:creator>
      <pubDate>Mon, 11 Mar 2024 10:23:54 +0000</pubDate>
      <link>https://forem.com/chigozieco/install-virtual-machine-with-vagrant-remotely-access-it-4k5j</link>
      <guid>https://forem.com/chigozieco/install-virtual-machine-with-vagrant-remotely-access-it-4k5j</guid>
      <description>&lt;p&gt;For this walk through I will be installing a new VM using Vagrant and then remotely accessing the VM using SSH with Vagrant.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Vagrant
&lt;/h2&gt;

&lt;p&gt;Installed as a layer of software between a virtualization tool (like VirtualBox, Docker, or Hyper-V) and a virtual machine (VM), Vagrant is an open-source program that makes it simple to create, configure, and manage boxes of virtual machines through an intuitive command interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is Vagrant Useful
&lt;/h2&gt;

&lt;p&gt;The process of setting up and maintaining virtual machines can be laborious and time-consuming. Even more difficult is replicating the virtual machine on an alternate server as well as if you have to replicate more than one virtual machine (VM).&lt;/p&gt;

&lt;p&gt;Vagrant is an effective tool that can make setting up and maintaining your development environment easier. Owing to the fact that it writes your configuration as code, it makes it possible for teams (and every other VM you create with your VagrantFile) have the same virtual machine setup across board.&lt;/p&gt;

&lt;p&gt;For a basic cheat sheet of every day vagrant commands that you will use in your day to day operations with vagrant, check out &lt;a href="https://dev.to/chigozieco/vagrant-cli-commands-cheat-sheet-f7c"&gt;this post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install a Hypervisor and Vagrant
&lt;/h2&gt;

&lt;p&gt;🌟 Before you begin, ensure you have a hypervisor such as Oracle Virtual box or VMWare. For this tutorial I will be making use of Oracle Virtual box, you can download it from &lt;a href="https://www.virtualbox.org/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;🌟 To install a VM with Vagrant, first install vagrant in your computer, get it from &lt;a href="https://developer.hashicorp.com/vagrant/install?product_intent=vagrant" rel="noopener noreferrer"&gt;vagrantup.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🌟 Vagrant is automatically added to your &lt;code&gt;env&lt;/code&gt; path so to check if the installation was successful, launch your terminal and enter the following command to output the installed version number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🌟 Once it's installed find the VM (box) you want to install on &lt;a href="https://app.vagrantup.com/boxes/search" rel="noopener noreferrer"&gt;vagrant cloud&lt;/a&gt; and note the name.&lt;/p&gt;

&lt;p&gt;🌟 For this installation I will be using ubuntu 20.04-LTS and we can see from vagrant cloud that it is called focal/64.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2uauulrk22znpphpc73o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2uauulrk22znpphpc73o.png" alt="Vagrant cloud" width="800" height="295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialize Vagrant
&lt;/h2&gt;

&lt;p&gt;🌟 The next thing to do is to initialize vagrant in other to create the Vagrantfile that would be used for our configuration.&lt;/p&gt;

&lt;p&gt;🌟 For each box I create with vagrant I create a specific directory for that box to keep things organised, that way the Vagrantfile (which is the configuration) of that box is stored in that directory and so before we initialize vagrant we will create the necessary directories.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; vagrant/boxes/ubuntu20.04-LTS
&lt;span class="nb"&gt;cd &lt;/span&gt;vagrant/boxes/ubuntu20.04-LTS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;&lt;br&gt;
the &lt;code&gt;p&lt;/code&gt; flag will make parent directories as needed and will not result in any error if already existing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frklzfxmdi2cg0bvuz4bu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frklzfxmdi2cg0bvuz4bu.png" alt="vagrant directory" width="701" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🌟 Now we can go ahead and initialize vagrant by running the command below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant init &amp;lt;box name&amp;gt;

vagrant init ubuntu/focal64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fttd8aqebvup8yjf9u8p1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fttd8aqebvup8yjf9u8p1.png" alt="Initialize vagrant" width="800" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🌟 Your Vagrantfile is now ready to be used to spin up your Virtual machine but before we do this I want to make some changes to our VM configuration in the Vagrantfile. Be sure to read the Vagrantfile so you understand the configurations of your VM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edit the Virtual Machine Configuration.
&lt;/h2&gt;

&lt;p&gt;I want my VM to use a private network where the IP will be assigned via DHCP and I also want to configure a shared folder between the Host OS and the Guest OS.&lt;/p&gt;

&lt;p&gt;These are the changes we will be making to the Vagrantfile. &lt;/p&gt;

&lt;p&gt;🌟 Using any editor of your choice, open the Vagrant file. I will be making use of vim.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vi Vagrantfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🌟 Scroll to the part of the file that says &lt;code&gt;Create a private network, which allows host-only access to the machine&lt;/code&gt; as shown in the image below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxzej6ksr93q2ytopm8bk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxzej6ksr93q2ytopm8bk.png" alt="vagrantfile" width="800" height="84"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what the part looks like and the line highlighted is the line we will change.&lt;/p&gt;

&lt;p&gt;🌟 First, uncomment it and edit it as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  config.vm.network &lt;span class="s2"&gt;"private_network"&lt;/span&gt;, &lt;span class="nb"&gt;type&lt;/span&gt;: &lt;span class="s2"&gt;"dhcp"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what it should now look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7z9qn2jjcz62ss0qe5k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7z9qn2jjcz62ss0qe5k.png" alt="Edited Vagrantfile" width="800" height="93"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🌟 The next change we will make is to add a shared folder between the host OS and the guest OS. Scroll down in the file and find where it says &lt;code&gt;Share an additional folder to the guest VM. The first argument is&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ykjwkooi6kje39eyb8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ykjwkooi6kje39eyb8e.png" alt="vagrantfile snippet" width="789" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Uncomment the highlighted line and choose a host folder of your choice, I want it in the same directory I am working on and so I will just make a slight change as can be seen below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  config.vm.synced_folder &lt;span class="s2"&gt;"./data"&lt;/span&gt;, &lt;span class="s2"&gt;"/vagrant_data"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk78b996imbpwpmby4atd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk78b996imbpwpmby4atd.png" alt="Edited vagrantfile snippet" width="800" height="138"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Ensure that the specified host directory already exist if not you will get an error message saying &lt;code&gt;There are errors in the configuration of this machine. Please fix the following errors and try again:&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vm:&lt;/code&gt;&lt;br&gt;
&lt;code&gt;* The host path of the shared folder is missing: ./data&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Save the Vagrantfile and we are ready to spin our VM up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the VM
&lt;/h2&gt;

&lt;p&gt;🌟 After saving the Vangrantfile configuration changes we made, we are ready to run the command we need to create our VM and we do this with the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note that the above command assumes that you are using Oracle virtual box as your hypervisor. If your were using VMware or any other virtualization tool the command to use is:&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant up &amp;lt;hypervisor&amp;gt;

vagrant up VMware
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above command will first download the ubuntu/focal64 VM image (or whichever image you selected) from the vagrant cloud and then it will start the VM.&lt;/p&gt;

&lt;p&gt;Also, it will generate an SSH key pair and adds the public key to the VM during this process so that we can SSH into the machine once it is up and running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpwls0eo7sxzta23t9itx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpwls0eo7sxzta23t9itx.png" alt="SSH Key" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you get this error displayed in the screenshot below, follow the next set of steps to correct the error.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr7ajqas65f9kmhuithd8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr7ajqas65f9kmhuithd8.png" alt="DHCP Error" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix DHCP Error
&lt;/h2&gt;

&lt;p&gt;🌟 Head over to your virtual box (hypervisor), launch it.&lt;br&gt;
🌟 Select the VM that Vagrant just created and on the top left hand click on &lt;code&gt;file&lt;/code&gt;.&lt;br&gt;
🌟 Click &lt;code&gt;tools&lt;/code&gt; and select &lt;code&gt;network manager&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkfmwbqk5aom24fpzw0ob.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkfmwbqk5aom24fpzw0ob.png" alt="Virtualbox interface" width="740" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🌟 Select the host-only network, either right click on it and click on remove or select it and then at the top right click on remove and remove when asked for confirmation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gwv92b08qiid82zmxze.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gwv92b08qiid82zmxze.png" alt="Virtualbox host only network" width="494" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🌟 When this is done go back to the terminal you are setting up your vagrant VM with and enter the below command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The configuration of your VM should complete successfully now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6rogux27xu1vj57uhlk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6rogux27xu1vj57uhlk.png" alt="VM installed" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  SSH into the Machine
&lt;/h2&gt;

&lt;p&gt;🌟 After the setup is complete you can now ssh into the machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzk0ax93bnpr2fdurwsr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzk0ax93bnpr2fdurwsr.png" alt="vagrant ssh" width="800" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Out of date Guest Additions
&lt;/h2&gt;

&lt;p&gt;If you see the message saying your guest additions are out of date, as seen in the image below, and you're like me that can't let things just be. Here's how to update your guest additions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0gcjzbxa3tslta1c9tkd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0gcjzbxa3tslta1c9tkd.png" alt="Out of date Guest Additions" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🌟 If you had previously SSH into the VM, exit the remote access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🌟 Run the vagrant vb-guest plugin&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant plugin &lt;span class="nb"&gt;install &lt;/span&gt;vagrant-vbguest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🌟 Next halt your VM by running the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant halt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🌟 Then run the &lt;code&gt;vagrant up&lt;/code&gt; command. You will see a lot of installations happening, allow it run its full course.&lt;/p&gt;

&lt;p&gt;🌟 Repeat the two commands again if need be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant halt
vagrant up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🌟 You should now see a message saying &lt;code&gt;GuestAdditions &amp;lt;version&amp;gt; running -- OK&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6x6f0dxxtjqp7b8gt49f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6x6f0dxxtjqp7b8gt49f.png" alt="Up to date Guest Additions" width="800" height="75"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🌟 You are good to go now, SSH into your machine and start enjoying tinkering around the Linux OS.&lt;/p&gt;

&lt;p&gt;If this post has helped you, consider supporting me: &lt;a href="https://buymeacoffee.com/chigozieco" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.buymeacoffee.com%2Fassets%2Fimg%2Fcustom_images%2Fyellow_img.png" alt="Buy Me A Coffee" width="170" height="37"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>vagrant</category>
      <category>cloud</category>
      <category>devops</category>
      <category>linux</category>
    </item>
    <item>
      <title>Remotely Accessing a Virtual Machine: SSH Key Pair</title>
      <dc:creator>ChigozieCO</dc:creator>
      <pubDate>Mon, 19 Feb 2024 10:02:00 +0000</pubDate>
      <link>https://forem.com/chigozieco/remotely-accessing-a-virtual-machine-ssh-key-pair-4en4</link>
      <guid>https://forem.com/chigozieco/remotely-accessing-a-virtual-machine-ssh-key-pair-4en4</guid>
      <description>&lt;p&gt;SSH (Secure Shell) is used for remotely accessing your server and it usually comes installed with a lot of Linux OS but where it is not installed, you can install it by installing the application OpenSSH. &lt;/p&gt;

&lt;p&gt;The remote system must have a version of SSH installed. The information in this post assumes the remote system uses OpenSSH, see how to install OpenSSH (client and server) below.&lt;/p&gt;

&lt;p&gt;SSH authentication can be via password or using private and public key pairs.&lt;/p&gt;

&lt;p&gt;While you can create a user with a password to login into any Linux system, sometime accessing the Linux system via that means is not possible either because it is not enabled by your system (in a corporate environment) or for some other reason and the only way is to SSH into the system.&lt;/p&gt;

&lt;p&gt;SSH keys come in pairs, the private and the public key. The private keys are always kept in the local machine that needs to connect to the remote system somewhere while the public keys can be shared with sysadmins to add to your corporate server or used in some form of authentication to give you access.&lt;/p&gt;

&lt;p&gt;On the remote machine, the public key is stored in a file called authorized keys. This is where the SSH service will check to see if the key on your machine matches the public key on the server before letting you in.&lt;/p&gt;

&lt;p&gt;You can generate ssh keys with the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keys are stored in the &lt;code&gt;.ssh&lt;/code&gt; folder inside a users home directory (&lt;code&gt;/home/$USER/.ssh&lt;/code&gt;). &lt;/p&gt;

&lt;h2&gt;
  
  
  Install OpenSSH (Client &amp;amp; Server)
&lt;/h2&gt;

&lt;p&gt;If you don't have SSH installed follow the steps below to install it, if you already do ignore the next few steps and do continue along to connect.&lt;/p&gt;

&lt;p&gt;🌟 Start your Linux machine in a normal start.&lt;/p&gt;

&lt;p&gt;🌟 Open the terminal and type the command below and enter your password when prompted for it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7t29cf82tra1gwnfes6p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7t29cf82tra1gwnfes6p.png" alt="update app repo" width="758" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🌟 You can search for the package before installing it with the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt search openssh-client &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The image below shows the one we want to make use of.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1m2dhfhjdznd9ifo3iui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1m2dhfhjdznd9ifo3iui.png" alt="Install openssh-client" width="757" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🌟 Next we install it using the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;openssh-client &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🌟 In the same way we install the openssh-server using the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;openssh-server &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🌟 When your installations are complete, confirm that the SSH service is running with the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl status ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9uhrlwz6fmteo4673lh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9uhrlwz6fmteo4673lh.png" alt="install openssh-server" width="735" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate SSH Keys
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;For this tutorial I will be working with two Virtual machines on my local computer, they are both Ubuntu. The remote location is shown on the terminal as &lt;code&gt;anulika@Goz&lt;/code&gt; and the local SSH host which is acting as my local computer is my vagrant VM which shows up on the terminal as &lt;code&gt;anulika@ubuntu-focal&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🌟 The first thing I want to do is to check for any existing keys my local user might have. This step is not required but it is recommended, type in the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; ~/.ssh/id_&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you do not see any output or if you see an output like that below then you do not have any keys present.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fthlsrpptp4n5n0nm3ac4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fthlsrpptp4n5n0nm3ac4.png" alt="no SSH key present" width="673" height="75"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you see an output listing out keys, then you have existing keys you should back them up so that you don't lose them incase you accidentally delete them.&lt;/p&gt;

&lt;p&gt;🌟 To create an SSH key, ensure you are in your local computer (the SSH host) and run the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will let you know that it is generating a public/private rsa key. By default it will use the rsa standard for all systems, if you want to use a different algorithm you can specify it with the &lt;code&gt;-t&lt;/code&gt; flag. It is also good practice to add a comment, you do this by using the &lt;code&gt;-C&lt;/code&gt; flag as seen below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; rsa &lt;span class="nt"&gt;-C&lt;/span&gt; &amp;lt;&lt;span class="s2"&gt;"Your comment"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🌟 You will be asked where you want to save the key, by default it will use the id_rsa file, I will leave the default so I press enter.&lt;/p&gt;

&lt;p&gt;🌟 When asked for a passphrase I just click enter because I don't want a passphrase. We're trying to avoid using passwords and so adding a passphrase will take me back to having to enter a password every time I want to connect. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that adding a passphrase is an additional security measure so that if anybody somehow gets hold of your computer with the private key, without knowing the passphrase they won't be granted access.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🌟 It will then go ahead and generate your keys.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjcqfdf5n0e8noklgocp5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjcqfdf5n0e8noklgocp5.png" alt="SSH keypair successfully created" width="752" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieve your Public Key
&lt;/h2&gt;

&lt;p&gt;🌟 When you run the &lt;code&gt;ls -la&lt;/code&gt; command now in the home directory of the user for which you just created an SSH key you should see the &lt;code&gt;.ssh&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /home/&amp;lt;your user&amp;gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F579zdzjfo54kibc7z5kz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F579zdzjfo54kibc7z5kz.png" alt="list directory content" width="682" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🌟 Move into the &lt;code&gt;.ssh&lt;/code&gt; directory and list it's contents and you will see both the &lt;code&gt;id_rsa&lt;/code&gt; and the &lt;code&gt;id_rsa.pub&lt;/code&gt; files. The former holds your private key while the latter holds your public key. This is where we will retrieve the public key from.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; .ssh
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🌟 Output the contents of the &lt;code&gt;id_rsa.pub&lt;/code&gt; file and copy it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;id_rsa.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmqj3hmvzcuch6qqq3hr4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmqj3hmvzcuch6qqq3hr4.png" alt="public key" width="800" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the key you will add to your remote machine in a file called authorized keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add the Public Key to the Remote Machine
&lt;/h2&gt;

&lt;p&gt;The public key for an SSH key pair needs to be added to a remote machine that you can SSH access. The remote machine will use the public key to decrypt the connection that the SSH host machine—your local computer— used its private key to encrypt.&lt;/p&gt;

&lt;p&gt;Transferring your public key to the remote system is a must. As a result, you need to either have an administrator on the remote system add the public key to the &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; file in your account or be able to log into the remote system using your established username and password/passphrase.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note&lt;/p&gt;

&lt;p&gt;If you already have an &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; file, probably because you have previously remotely accessed that machine using SSH key authentication, all you need to do in this section is to edit the &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; file and add your new public key. In the authorized_keys file, add the new key in a new line and then save the file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🌟 Head over to your remote machine, open your terminal and navigate to the home directory of your user of choice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🌟 List it's contents to check if you have an &lt;code&gt;.ssh&lt;/code&gt; directory. If you have one, list its contents to see if it contains an &lt;code&gt;authorized_keys&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; ~
&lt;span class="nb"&gt;cd&lt;/span&gt; .ssh // &lt;span class="c"&gt;#if the .ssh file exists&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If the &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; file exists, skip the next step and continue on to edit the file and place you public key in it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🌟 If your account on the remote system doesn't already contain a &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; file, create one; on the command line, enter the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~
&lt;span class="nb"&gt;mkdir&lt;/span&gt; .ssh
&lt;span class="nb"&gt;cd&lt;/span&gt; .ssh
&lt;span class="nb"&gt;touch &lt;/span&gt;authorized_keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With those 4 commands above we simply navigated to the user's home directory, created the &lt;code&gt;.ssh&lt;/code&gt; directory, entered into the directory and created an &lt;code&gt;authorized_keys&lt;/code&gt; file. &lt;/p&gt;

&lt;p&gt;🌟 Next we would use a file editor to add our public key to the &lt;code&gt;authorized_keys&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vi authorized_keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste your public key in the file, save and close it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3uuvd9tm49spcyrttsuz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3uuvd9tm49spcyrttsuz.png" alt="add public key" width="790" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieve IP Address of Your Remote machine
&lt;/h2&gt;

&lt;p&gt;🌟 You need to know the IP address of the remote machine, run the below command to obtain it's IP address.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before you start protesting about needing to have the IP address of the remote machine (I mean if you had access to the remote machine why would you need to connect to it remotely right? Wrong!!)&lt;br&gt;
If you know anything about hacking, the first thing you need to do when to begin hacking any machine or server is to find the IP address of that server.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip &lt;span class="nt"&gt;--brief&lt;/span&gt; addr show
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the IP address of the interface you want to use, leave your machine turned on. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If like me you are working with two VMs on the same host machine, ensure that the IP address you choose is different from that of the VM you are using as your SSH host.&lt;br&gt;
If you use NAT adapter and you notice that the two VMs have the same IP address power them off and add another adapter attached to &lt;code&gt;Host-only Adapter&lt;/code&gt; apply the changes and restart your VM. Do this for the VMs, this way they will both get different IP addresses.&lt;br&gt;
Use this unique IP address for the next step.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Access the Remote Machine Using the SSH Key Pair
&lt;/h2&gt;

&lt;p&gt;Now we have everything set up and if you followed the steps correctly, you should too.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Return to your local computer, the SSH host, which has the private key and type the below command in the terminal.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &amp;lt;user&amp;gt;@&amp;lt;ip address&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwmkg1z381bqxkm85qni2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwmkg1z381bqxkm85qni2.png" alt="ssh keypair connection" width="764" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The local SSH host which is acting as my local computer is my vagrant VM which shows up on the terminal as &lt;code&gt;anulika@ubuntu-focal&lt;/code&gt; and the remote location shows on the terminal as &lt;code&gt;anulika@Goz&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you look at what the arrows in the screenshot above points to you will notice the change when the ssh remote access was successful.&lt;/p&gt;

&lt;p&gt;You have now successfully implemented remotely accessing a server using SSH key pairs, go on ahead and brag about your abilities champ.&lt;/p&gt;

&lt;p&gt;If this post has helped you, consider supporting me: &lt;a href="https://buymeacoffee.com/chigozieco" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.buymeacoffee.com%2Fassets%2Fimg%2Fcustom_images%2Fyellow_img.png" alt="Buy Me A Coffee" width="170" height="37"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ssh</category>
      <category>virtualmachine</category>
      <category>cloudcomputing</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
