<?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>DEV Community: Bernard Chika Uwaezuoke</title>
    <description>The latest articles on DEV Community by Bernard Chika Uwaezuoke (@donhadley22).</description>
    <link>https://dev.to/donhadley22</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1077992%2Fc68445db-79e3-411a-a3f8-555e92e383d6.jpeg</url>
      <title>DEV Community: Bernard Chika Uwaezuoke</title>
      <link>https://dev.to/donhadley22</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/donhadley22"/>
    <language>en</language>
    <item>
      <title>Building a 7-Service Docker Compose Incident Response Platform</title>
      <dc:creator>Bernard Chika Uwaezuoke</dc:creator>
      <pubDate>Fri, 03 Jul 2026 16:03:03 +0000</pubDate>
      <link>https://dev.to/donhadley22/building-a-7-service-docker-compose-incident-response-platform-2k26</link>
      <guid>https://dev.to/donhadley22/building-a-7-service-docker-compose-incident-response-platform-2k26</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this project, I built a mini production-style &lt;strong&gt;Incident Response Platform&lt;/strong&gt; using Docker Compose.&lt;/p&gt;

&lt;p&gt;The goal was not just to run one container. The goal was to model how real applications are often structured in DevOps and platform engineering environments: multiple services, clear separation of responsibilities, persistent storage, background processing, routing, health checks, and monitoring.&lt;/p&gt;

&lt;p&gt;The final platform has &lt;strong&gt;seven services&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Nginx edge reverse proxy&lt;/li&gt;
&lt;li&gt;Frontend web UI&lt;/li&gt;
&lt;li&gt;FastAPI backend API&lt;/li&gt;
&lt;li&gt;Background worker&lt;/li&gt;
&lt;li&gt;PostgreSQL database&lt;/li&gt;
&lt;li&gt;Redis cache and queue&lt;/li&gt;
&lt;li&gt;Prometheus monitoring&lt;/li&gt;
&lt;/ol&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F63f196rpq06yngqeutqp.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F63f196rpq06yngqeutqp.png" alt="Cover image showing the 7-service Docker Compose incident response platform" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Problem Does This Project Solve?
&lt;/h2&gt;

&lt;p&gt;Every engineering team eventually deals with incidents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An API becomes slow&lt;/li&gt;
&lt;li&gt;A payment service fails&lt;/li&gt;
&lt;li&gt;A database starts timing out&lt;/li&gt;
&lt;li&gt;Error rates increase after a deployment&lt;/li&gt;
&lt;li&gt;Users report that an application is unavailable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In many teams, these issues are first tracked manually in chats, spreadsheets, or random notes. That works for very small teams, but it quickly becomes messy.&lt;/p&gt;

&lt;p&gt;This project provides a simple incident management workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an incident&lt;/li&gt;
&lt;li&gt;Assign severity&lt;/li&gt;
&lt;li&gt;Assign ownership&lt;/li&gt;
&lt;li&gt;Acknowledge the incident&lt;/li&gt;
&lt;li&gt;Resolve the incident&lt;/li&gt;
&lt;li&gt;Store incident records&lt;/li&gt;
&lt;li&gt;Process background notifications&lt;/li&gt;
&lt;li&gt;Expose metrics for monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is intentionally small, but the architecture follows patterns used in real systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Architecture
&lt;/h2&gt;

&lt;p&gt;The platform runs as a Docker Compose stack. A browser connects to Nginx, and Nginx routes requests either to the frontend or the API.&lt;/p&gt;

&lt;p&gt;The API talks to PostgreSQL for persistent storage and Redis for caching and background jobs. A worker consumes jobs from Redis. Prometheus scrapes metrics from the API.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhd6oghx7wlzralzjaje8.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhd6oghx7wlzralzjaje8.png" alt="Architecture diagram for the incident response platform" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The high-level request flow looks 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;Browser
  -&amp;gt; Nginx edge service
    -&amp;gt; Frontend web UI
    -&amp;gt; FastAPI backend
      -&amp;gt; PostgreSQL
      -&amp;gt; Redis
        -&amp;gt; Worker
      -&amp;gt; Prometheus metrics endpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Project Folder Structure
&lt;/h2&gt;

&lt;p&gt;I organized the project so each concern has its own place.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1fan9s56bzm9iyf5pnfc.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1fan9s56bzm9iyf5pnfc.png" alt="Project folder structure in VS Code" width="799" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The important folders are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;incident-response-platform
|-- docker-compose.yml
|-- infra
|   |-- nginx
|   |   `-- nginx.conf
|   `-- prometheus
|       `-- prometheus.yml
|-- services
|   |-- api
|   |   |-- app
|   |   |-- Dockerfile
|   |   |-- requirements.txt
|   |   `-- worker.py
|   `-- frontend
|       |-- Dockerfile
|       |-- index.html
|       |-- app.js
|       `-- styles.css
|-- .env.example
`-- README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure keeps infrastructure configuration separate from application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining the Seven Services in Docker Compose
&lt;/h2&gt;

&lt;p&gt;The heart of the project is the &lt;code&gt;docker-compose.yml&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;edge&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;nginx:1.27-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;incident-edge&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;frontend&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;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;8081:80"&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;./infra/nginx/nginx.conf:/etc/nginx/nginx.conf: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;incident-net&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;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./services/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;incident-frontend&lt;/span&gt;
    &lt;span class="na"&gt;expose&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"&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;incident-net&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;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./services/api&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;incident-api&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;.env&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;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://incident_app:incident_password@postgres:5432/incident_response&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis://redis:6379/0&lt;/span&gt;
      &lt;span class="na"&gt;ENVIRONMENT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
      &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
    &lt;span class="na"&gt;expose&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"&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&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;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;import&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;urllib.request;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;urllib.request.urlopen('http://localhost:8000/health').read()"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;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;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;20s&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;incident-net&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;worker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./services/api&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;incident-worker&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worker.py"&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;.env&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;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://incident_app:incident_password@postgres:5432/incident_response&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis://redis:6379/0&lt;/span&gt;
      &lt;span class="na"&gt;ENVIRONMENT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
      &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&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;incident-net&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;postgres&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:16-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;incident-postgres&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;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;incident_response&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;incident_app&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;incident_password&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;5432:5432"&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;postgres-data:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&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;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;incident_app&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-d&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;incident_response"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&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;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&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;incident-net&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;redis&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;redis:7-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;incident-redis&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-server"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--appendonly"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yes"&lt;/span&gt;&lt;span class="pi"&gt;]&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;6379:6379"&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;redis-data:/data&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&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;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-cli"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&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;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&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;incident-net&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;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:v2.55.1&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;incident-prometheus&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="s2"&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--storage.tsdb.path=/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;--web.enable-lifecycle"&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;9090:9090"&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;./infra/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prometheus-data:/prometheus&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;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;incident-net&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;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;incident-net&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;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redis-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Compose file gives the application a production-style shape:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nginx is the only public application entry point&lt;/li&gt;
&lt;li&gt;The frontend and API run as separate services&lt;/li&gt;
&lt;li&gt;PostgreSQL and Redis have health checks&lt;/li&gt;
&lt;li&gt;Data is stored in named Docker volumes&lt;/li&gt;
&lt;li&gt;Services communicate over a private Docker network&lt;/li&gt;
&lt;li&gt;Prometheus collects metrics from the API&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Running the Stack
&lt;/h2&gt;

&lt;p&gt;To start the platform, I used:&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;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command builds the custom API, worker, and frontend images, pulls the required base images, creates the network, creates volumes, and starts all services.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02c99hx4gmt9dcgtjp1g.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02c99hx4gmt9dcgtjp1g.png" alt="Docker Compose logs showing services starting and health checks passing" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To check the containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F89fkg0ewnrctoddlbyj9.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F89fkg0ewnrctoddlbyj9.png" alt="docker ps output showing all seven containers running" width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main URLs are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application: http://localhost:8081
API docs:    http://localhost:8081/api/docs
Prometheus:  http://localhost:9090
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used port &lt;code&gt;8081&lt;/code&gt; because Jenkins was already running on port &lt;code&gt;8080&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Web Application
&lt;/h2&gt;

&lt;p&gt;Once the stack is running, the browser UI is available at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8081
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F40f8xqwd9i3j4hnjd6yb.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F40f8xqwd9i3j4hnjd6yb.png" alt="Incident Response Platform web UI" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the UI, an operator can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new incident&lt;/li&gt;
&lt;li&gt;Set the affected service&lt;/li&gt;
&lt;li&gt;Choose the severity&lt;/li&gt;
&lt;li&gt;Assign an owner&lt;/li&gt;
&lt;li&gt;Acknowledge the incident&lt;/li&gt;
&lt;li&gt;Resolve the incident&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When an incident is created, the frontend sends a request to the API through Nginx.&lt;/p&gt;

&lt;h2&gt;
  
  
  The API
&lt;/h2&gt;

&lt;p&gt;The backend API is built with FastAPI.&lt;/p&gt;

&lt;p&gt;FastAPI gives the project automatic Swagger documentation, which is useful for testing the API directly from the browser.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq0lbozo6kv5yaql99tm1.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq0lbozo6kv5yaql99tm1.png" alt="FastAPI Swagger documentation for the incident platform" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The API exposes endpoints like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET    /health
GET    /incidents
POST   /incidents
PATCH  /incidents/{incident_id}
GET    /metrics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/health&lt;/code&gt; endpoint is used by Docker health checks.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/metrics&lt;/code&gt; endpoint exposes Prometheus-compatible metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background Jobs with Redis and Worker
&lt;/h2&gt;

&lt;p&gt;When a new incident is created, the API does not do all the work inside the request.&lt;/p&gt;

&lt;p&gt;Instead, it stores the incident in PostgreSQL and pushes a job into Redis.&lt;/p&gt;

&lt;p&gt;The worker service listens for jobs from Redis and processes them asynchronously.&lt;/p&gt;

&lt;p&gt;In this sample project, the worker simulates notifying the platform team. In a real-world system, this could be replaced with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slack notifications&lt;/li&gt;
&lt;li&gt;Microsoft Teams notifications&lt;/li&gt;
&lt;li&gt;Email alerts&lt;/li&gt;
&lt;li&gt;PagerDuty escalation&lt;/li&gt;
&lt;li&gt;ServiceNow ticket creation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This pattern is important because web requests should stay fast. Slow or external tasks should usually run in the background.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxiyrlhi7h6jnjia13voj.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxiyrlhi7h6jnjia13voj.png" alt="Runtime logs showing incident creation and worker processing" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring with Prometheus
&lt;/h2&gt;

&lt;p&gt;Prometheus is included as the monitoring service.&lt;/p&gt;

&lt;p&gt;It scrapes the API metrics endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://api:8000/metrics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some example metrics exposed by the API are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;incidents_created_total
incidents_resolved_total
open_incidents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prometheus is available locally at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:9090
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the project a real observability layer instead of only relying on logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing to GitHub
&lt;/h2&gt;

&lt;p&gt;After building and testing the project locally, I initialized Git, committed the code, created a GitHub repository, and pushed the project.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdr5c8m6nndxmg2cxyfsq.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdr5c8m6nndxmg2cxyfsq.png" alt="GitHub repository screenshot" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The basic Git workflow was:&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;"Initial Docker Compose incident platform"&lt;/span&gt;
gh repo create incident-response-platform &lt;span class="nt"&gt;--private&lt;/span&gt; &lt;span class="nt"&gt;--source&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--remote&lt;/span&gt; origin &lt;span class="nt"&gt;--push&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to share your own project publicly, create the repository as public or change the visibility later in GitHub settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  DevOps Skills Practiced
&lt;/h2&gt;

&lt;p&gt;This project helped me practice several real DevOps concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker Compose for multi-service applications&lt;/li&gt;
&lt;li&gt;Nginx as a reverse proxy&lt;/li&gt;
&lt;li&gt;FastAPI backend development&lt;/li&gt;
&lt;li&gt;PostgreSQL persistence&lt;/li&gt;
&lt;li&gt;Redis caching and background queues&lt;/li&gt;
&lt;li&gt;Background worker architecture&lt;/li&gt;
&lt;li&gt;Docker health checks&lt;/li&gt;
&lt;li&gt;Docker networks&lt;/li&gt;
&lt;li&gt;Docker volumes&lt;/li&gt;
&lt;li&gt;Prometheus metrics&lt;/li&gt;
&lt;li&gt;Git and GitHub workflow&lt;/li&gt;
&lt;li&gt;Production-style application thinking&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F028hstgwdgfauqwwdfpe.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F028hstgwdgfauqwwdfpe.png" alt="Overview of DevOps skills learned from the project" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful Commands
&lt;/h2&gt;

&lt;p&gt;Start the project:&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;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run in detached mode:&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;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check running containers:&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 ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;View all logs:&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 logs &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;View only API logs:&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 logs &lt;span class="nt"&gt;-f&lt;/span&gt; api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;View worker logs:&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 logs &lt;span class="nt"&gt;-f&lt;/span&gt; worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stop the stack:&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stop and remove volumes:&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="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I Would Add Next
&lt;/h2&gt;

&lt;p&gt;This project is a strong foundation, but there are several ways to improve it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add Grafana dashboards&lt;/li&gt;
&lt;li&gt;Add authentication&lt;/li&gt;
&lt;li&gt;Add real Slack or Teams notifications&lt;/li&gt;
&lt;li&gt;Add CI/CD with GitHub Actions&lt;/li&gt;
&lt;li&gt;Add container image scanning&lt;/li&gt;
&lt;li&gt;Add automated tests&lt;/li&gt;
&lt;li&gt;Add rate limiting in Nginx&lt;/li&gt;
&lt;li&gt;Add TLS for HTTPS&lt;/li&gt;
&lt;li&gt;Deploy it to a cloud environment&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This project shows how Docker Compose can be used to build more than a simple container demo.&lt;/p&gt;

&lt;p&gt;By combining Nginx, FastAPI, PostgreSQL, Redis, a worker, and Prometheus, I created a small but realistic incident response platform.&lt;/p&gt;

&lt;p&gt;The biggest lesson is that DevOps is not only about running containers. It is about designing systems that are organized, observable, resilient, and easier to operate.&lt;/p&gt;

&lt;p&gt;That is what this project demonstrates in a practical way.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>fastapi</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Building Decoupled Event-Driven Microservices on AWS with SNS, SQS, Lambda, and DynamoDB</title>
      <dc:creator>Bernard Chika Uwaezuoke</dc:creator>
      <pubDate>Thu, 02 Jul 2026 14:44:49 +0000</pubDate>
      <link>https://dev.to/donhadley22/building-decoupled-event-driven-microservices-on-aws-with-sns-sqs-lambda-and-dynamodb-4jnj</link>
      <guid>https://dev.to/donhadley22/building-decoupled-event-driven-microservices-on-aws-with-sns-sqs-lambda-and-dynamodb-4jnj</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Modern applications often begin as a single service where one request triggers several operations sequentially. An order-processing application, for example, might receive an order, process its payment, update inventory, and send a notification within the same synchronous request.&lt;/p&gt;

&lt;p&gt;This design is simple at the beginning, but it becomes increasingly fragile as the application grows.&lt;/p&gt;

&lt;p&gt;What happens when the payment provider is slow? What if the notification service is unavailable? Should an inventory failure prevent the application from accepting an otherwise valid order? How do we add a new analytics, fraud detection, or shipping service without modifying the original order service?&lt;/p&gt;

&lt;p&gt;These are some of the problems that event-driven microservices are designed to solve.&lt;/p&gt;

&lt;p&gt;In this project, I built a serverless, event-driven order-processing system using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon API Gateway&lt;/li&gt;
&lt;li&gt;AWS Lambda&lt;/li&gt;
&lt;li&gt;Amazon Simple Notification Service&lt;/li&gt;
&lt;li&gt;Amazon Simple Queue Service&lt;/li&gt;
&lt;li&gt;Amazon DynamoDB&lt;/li&gt;
&lt;li&gt;Amazon CloudWatch&lt;/li&gt;
&lt;li&gt;AWS Serverless Application Model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The central architectural decision was to use Amazon SNS for event distribution and Amazon SQS for durable, service-specific message buffering.&lt;/p&gt;

&lt;p&gt;The result is an architecture in which payment, inventory, and notification services can operate, fail, recover, and scale independently.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Project repository: &lt;a href="https://github.com/Donhadley22/aws-sns-sqs-microservices" rel="noopener noreferrer"&gt;https://github.com/Donhadley22/aws-sns-sqs-microservices&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note: If the repository remains private, readers will not be able to access the source code until it is made public.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The problem with tightly coupled microservices
&lt;/h2&gt;

&lt;p&gt;Consider the following synchronous workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
  |
  v
Order Service
  |
  v
Payment Service
  |
  v
Inventory Service
  |
  v
Notification Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although the application has been divided into services, the services are still operationally dependent on one another.&lt;/p&gt;

&lt;p&gt;The Order Service cannot finish until Payment responds. Payment might depend on Inventory, and Notification might become part of the same request chain.&lt;/p&gt;

&lt;p&gt;This creates several problems.&lt;/p&gt;

&lt;h4&gt;
  
  
  Increased response time
&lt;/h4&gt;

&lt;p&gt;The client must wait for every downstream operation to complete.&lt;/p&gt;

&lt;p&gt;If payment takes two seconds, inventory takes one second, and notification takes three seconds, the request can take six seconds or longer.&lt;/p&gt;

&lt;h4&gt;
  
  
  Failure propagation
&lt;/h4&gt;

&lt;p&gt;A notification failure could cause the entire order request to fail, even though the order and payment were successful.&lt;/p&gt;

&lt;h4&gt;
  
  
  Difficult scaling
&lt;/h4&gt;

&lt;p&gt;Payment traffic, notification traffic, and inventory traffic may have different scaling patterns. A synchronous architecture makes it harder to scale them independently.&lt;/p&gt;

&lt;h4&gt;
  
  
  Difficult service expansion
&lt;/h4&gt;

&lt;p&gt;Adding fraud detection, shipping, analytics, or audit services usually requires modifying the producing service or inserting more synchronous calls.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tight runtime dependency
&lt;/h4&gt;

&lt;p&gt;Every downstream service must be available at the same time as the upstream service.&lt;/p&gt;

&lt;p&gt;This is precisely where messaging services become valuable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution overview
&lt;/h2&gt;

&lt;p&gt;The project uses the following architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
   |
   v
Amazon API Gateway
   |
   v
Order Service Lambda
   |
   +------&amp;gt; Orders DynamoDB Table
   |
   v
Amazon SNS Order Events Topic
   |
   +------&amp;gt; Payment SQS Queue ------&amp;gt; Payment Lambda
   |                                     |
   |                                     +------&amp;gt; Payments Table
   |                                     |
   |                                     +------&amp;gt; SNS outcome event
   |
   +------&amp;gt; Inventory SQS Queue ----&amp;gt; Inventory Lambda
   |                                     |
   |                                     +------&amp;gt; Inventory Table
   |                                     |
   |                                     +------&amp;gt; SNS outcome event
   |
   +------&amp;gt; Notification SQS Queue -&amp;gt; Notification Lambda
                                             |
                                             +------&amp;gt; Notifications Table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each SQS queue also has a dedicated dead-letter queue.&lt;/p&gt;

&lt;p&gt;Amazon SNS and Amazon SQS are often used together because SNS can distribute one published event to multiple subscribers, while SQS stores each service's copy until that service is ready to process it. This means the producer and consumers do not need to be available at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the responsibility of each service
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Order Service
&lt;/h3&gt;

&lt;p&gt;The Order Service is exposed through Amazon API Gateway.&lt;/p&gt;

&lt;p&gt;It is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Receiving the HTTP request&lt;/li&gt;
&lt;li&gt;Validating the order payload&lt;/li&gt;
&lt;li&gt;Generating an order ID&lt;/li&gt;
&lt;li&gt;Calculating the order total&lt;/li&gt;
&lt;li&gt;Writing the order to DynamoDB&lt;/li&gt;
&lt;li&gt;Publishing an OrderCreated event to Amazon SNS&lt;/li&gt;
&lt;li&gt;Returning a 202 Accepted response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The service does not directly invoke Payment, Inventory, or Notification.&lt;/p&gt;

&lt;p&gt;That is a critical design decision.&lt;/p&gt;

&lt;p&gt;The Order Service knows that an order has been created, but it does not need to know every service interested in that event.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Payment Service
&lt;/h3&gt;

&lt;p&gt;The Payment Service receives messages from its dedicated Payment SQS queue.&lt;/p&gt;

&lt;p&gt;It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads the OrderCreated event&lt;/li&gt;
&lt;li&gt;Processes the payment rule&lt;/li&gt;
&lt;li&gt;Stores the payment result&lt;/li&gt;
&lt;li&gt;Publishes either PaymentCompleted or PaymentFailed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because Payment owns its own queue, a payment-processing slowdown does not block Inventory or prevent the Order Service from accepting new requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Inventory Service
&lt;/h3&gt;

&lt;p&gt;The Inventory Service independently receives its own copy of the OrderCreated event.&lt;/p&gt;

&lt;p&gt;It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checks the requested items&lt;/li&gt;
&lt;li&gt;Simulates stock reservation&lt;/li&gt;
&lt;li&gt;Stores the inventory outcome&lt;/li&gt;
&lt;li&gt;Publishes either InventoryReserved or InventoryFailed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Payment and Inventory can therefore process the same order in parallel.&lt;/p&gt;

&lt;p&gt;Neither service needs to call the other.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Notification Service
&lt;/h3&gt;

&lt;p&gt;The Notification Service is interested only in the results generated by Payment and Inventory.&lt;/p&gt;

&lt;p&gt;It subscribes to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PaymentCompleted
PaymentFailed
InventoryReserved
InventoryFailed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service records a notification for the customer based on the event it receives.&lt;/p&gt;

&lt;p&gt;In a production environment, this could be extended to deliver:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Email through Amazon SES&lt;/li&gt;
&lt;li&gt;SMS through Amazon SNS&lt;/li&gt;
&lt;li&gt;Mobile push notifications&lt;/li&gt;
&lt;li&gt;Slack or Microsoft Teams notifications&lt;/li&gt;
&lt;li&gt;Webhook callbacks&lt;/li&gt;
&lt;li&gt;In-application notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why use both SNS and SQS?
&lt;/h2&gt;

&lt;p&gt;A frequent question from students is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why not publish directly to SQS, or trigger every service directly from SNS?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer lies in the different responsibilities of the two services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Amazon SNS provides fan-out
&lt;/h3&gt;

&lt;p&gt;Amazon SNS follows a publish-subscribe model.&lt;/p&gt;

&lt;p&gt;The publisher sends one event to a topic. The topic distributes copies to all matching subscriptions.&lt;/p&gt;

&lt;p&gt;The Order Service therefore publishes only once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service -&amp;gt; Order Events SNS Topic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SNS then distributes the event to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payment Queue
Inventory Queue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additional consumers can be added later without changing the Order Service.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fraud Detection Queue
Analytics Queue
Shipping Queue
Audit Queue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Amazon SQS provides durability and back-pressure
&lt;/h3&gt;

&lt;p&gt;Amazon SQS stores messages until a consumer successfully processes them or their retention period expires.&lt;/p&gt;

&lt;p&gt;This is important because consumers do not always process traffic at the same speed.&lt;/p&gt;

&lt;p&gt;Suppose the system receives 10,000 orders during a promotional campaign. The Order Service can continue accepting requests while the Payment queue temporarily accumulates messages.&lt;/p&gt;

&lt;p&gt;Payment workers can process the backlog according to their available concurrency.&lt;/p&gt;

&lt;p&gt;The queue absorbs the traffic spike instead of allowing the spike to overload the Payment Service.&lt;/p&gt;

&lt;p&gt;This is known as back-pressure handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-step event flow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: The client submits an order
&lt;/h3&gt;

&lt;p&gt;The client sends:&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="s"&gt;POST /orders&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example request:&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;"customerEmail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"student@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&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;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LAPTOP-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;850&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;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MOUSE-002"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&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;"paymentOutcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"approved"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inventoryOutcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reserved"&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;API Gateway forwards the request to the Order Service Lambda function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: The order is stored
&lt;/h3&gt;

&lt;p&gt;The Order Service generates an order ID and stores the order in the Orders DynamoDB table.&lt;/p&gt;

&lt;p&gt;Its initial state changes from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ORDER_RECEIVED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EVENT_PUBLISHED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;after the event is successfully published.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: The Order Service publishes an event
&lt;/h3&gt;

&lt;p&gt;The event follows a consistent envelope:&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;"eventId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unique-event-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eventType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OrderCreated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eventVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order-service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"occurredAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-02T10:00:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&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;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unique-order-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"customerEmail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"student@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"items"&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;"totalAmount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&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;This envelope provides several important fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;eventId supports tracing and deduplication.&lt;/li&gt;
&lt;li&gt;eventType identifies the business event.&lt;/li&gt;
&lt;li&gt;eventVersion supports schema evolution.&lt;/li&gt;
&lt;li&gt;source identifies the producer.&lt;/li&gt;
&lt;li&gt;occurredAt records when the event happened.&lt;/li&gt;
&lt;li&gt;data contains the business payload.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A well-defined event contract is essential because events become APIs between services.&lt;/p&gt;

&lt;p&gt;Changing an event structure without version control can break several consumers simultaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: SNS applies subscription filters
&lt;/h3&gt;

&lt;p&gt;The Payment subscription receives only OrderCreated events:&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;FilterPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;eventType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OrderCreated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Inventory subscription uses the same filter:&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;FilterPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;eventType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OrderCreated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Notification subscription receives only outcome events:&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;FilterPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;eventType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PaymentCompleted&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PaymentFailed&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;InventoryReserved&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;InventoryFailed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, an SNS subscription receives every message published to its topic. A filter policy limits delivery to messages whose attributes or body match the subscription's conditions.&lt;/p&gt;

&lt;p&gt;Filtering prevents irrelevant events from reaching queues and reduces unnecessary Lambda invocations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Lambda processes messages from SQS
&lt;/h3&gt;

&lt;p&gt;Each queue is configured as an event source for its corresponding Lambda function.&lt;/p&gt;

&lt;p&gt;For example:&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;PaymentQueueEvent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SQS&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Queue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;PaymentQueue.Arn&lt;/span&gt;
    &lt;span class="na"&gt;BatchSize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;MaximumBatchingWindowInSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;FunctionResponseTypes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReportBatchItemFailures&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Lambda event source mapping polls SQS and sends messages to the function in batches.&lt;/p&gt;

&lt;p&gt;The Payment and Inventory services use a batch size of five, while Notification uses a larger batch because notification records are lightweight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling failures correctly
&lt;/h2&gt;

&lt;p&gt;Building an event-driven system is not only about delivering messages. It is about designing what happens when processing fails.&lt;/p&gt;

&lt;p&gt;This project separates failures into two categories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Business failures
&lt;/h3&gt;

&lt;p&gt;A business failure is a valid result that the application understands.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payment declined
Product out of stock
Address validation failed
Customer account suspended
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These should not normally be sent to a dead-letter queue.&lt;/p&gt;

&lt;p&gt;The service should process the message successfully and publish a business outcome such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PaymentFailed
InventoryFailed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows other services to react to the failure.&lt;/p&gt;

&lt;p&gt;For example, Notification can inform the customer that payment was declined.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical failures
&lt;/h3&gt;

&lt;p&gt;A technical failure means the message could not be processed because of an unexpected problem.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database unavailable
Malformed event
Dependency timeout
Permission failure
Uncaught application exception
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These failures should be retried.&lt;/p&gt;

&lt;p&gt;If processing continues to fail, the message should eventually move to a dead-letter queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dead-letter queues
&lt;/h2&gt;

&lt;p&gt;Every service queue in this project has its own DLQ:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payment Queue ------&amp;gt; Payment DLQ
Inventory Queue ----&amp;gt; Inventory DLQ
Notification Queue -&amp;gt; Notification DLQ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The redrive policy is configured as follows:&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;RedrivePolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deadLetterTargetArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;PaymentDeadLetterQueue.Arn&lt;/span&gt;
  &lt;span class="na"&gt;maxReceiveCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After three unsuccessful processing attempts, SQS moves the message to the relevant DLQ.&lt;/p&gt;

&lt;p&gt;Dedicated DLQs provide failure isolation. A poison message for Payment does not pollute Inventory or Notification processing.&lt;/p&gt;

&lt;p&gt;The DLQ retention period in this project is longer than the source queue retention period, which follows AWS guidance for preserving failed messages long enough for investigation.&lt;/p&gt;

&lt;p&gt;CloudWatch alarms monitor the approximate number of visible messages in each DLQ.&lt;/p&gt;

&lt;p&gt;In production, those alarms should notify the operations team through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An SNS alert topic&lt;/li&gt;
&lt;li&gt;Email&lt;/li&gt;
&lt;li&gt;Slack&lt;/li&gt;
&lt;li&gt;Microsoft Teams&lt;/li&gt;
&lt;li&gt;PagerDuty&lt;/li&gt;
&lt;li&gt;An incident-management platform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A DLQ should never become a graveyard where failed messages are ignored. Every DLQ needs an operational process for investigation, correction, replay, or deletion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Partial batch failure handling
&lt;/h2&gt;

&lt;p&gt;Assume Lambda receives five SQS messages and only one fails.&lt;/p&gt;

&lt;p&gt;Without partial batch handling, the entire batch may be retried, including the four messages that were processed successfully.&lt;/p&gt;

&lt;p&gt;This creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Duplicate work&lt;/li&gt;
&lt;li&gt;Increased cost&lt;/li&gt;
&lt;li&gt;Reduced throughput&lt;/li&gt;
&lt;li&gt;More complicated data handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project enables:&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;FunctionResponseTypes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReportBatchItemFailures&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each consumer returns only the failed message IDs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;batchItemFailures&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="na"&gt;itemIdentifier&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;messageId&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;Lambda then makes only those failed messages available for retry.&lt;/p&gt;

&lt;p&gt;AWS recommends partial batch responses to avoid reprocessing successful messages when one record in an SQS batch fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotency is not optional
&lt;/h2&gt;

&lt;p&gt;Amazon SQS standard queues provide at-least-once processing semantics. This means the same message may occasionally be delivered more than once.&lt;/p&gt;

&lt;p&gt;Therefore, a consumer must be able to process duplicate messages safely.&lt;/p&gt;

&lt;p&gt;Imagine a Payment Service that charges a customer every time it receives a message. If the message is delivered twice, the customer could be charged twice.&lt;/p&gt;

&lt;p&gt;That is unacceptable.&lt;/p&gt;

&lt;p&gt;In this project, the Payment Service uses a conditional DynamoDB write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;documentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PutCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PAYMENTS_TABLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;paymentResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ConditionExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;attribute_not_exists(orderId)&lt;/span&gt;&lt;span class="dl"&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 first message stores the payment.&lt;/p&gt;

&lt;p&gt;A duplicate message encounters the existing orderId, preventing a second payment record from being created.&lt;/p&gt;

&lt;p&gt;The project also generates deterministic outcome event IDs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;eventId&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;orderCreatedEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:payment-outcome`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Payment stores its result but temporarily fails while publishing the outcome event, the original SQS message is retried.&lt;/p&gt;

&lt;p&gt;On retry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The duplicate database write is recognized.&lt;/li&gt;
&lt;li&gt;The service safely republishes the outcome.&lt;/li&gt;
&lt;li&gt;The deterministic event ID allows downstream consumers to detect the duplicate outcome.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Idempotency should be considered at every side-effect boundary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database writes&lt;/li&gt;
&lt;li&gt;Payment requests&lt;/li&gt;
&lt;li&gt;Inventory reservations&lt;/li&gt;
&lt;li&gt;Emails&lt;/li&gt;
&lt;li&gt;Webhooks&lt;/li&gt;
&lt;li&gt;External API calls&lt;/li&gt;
&lt;li&gt;Event publication&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Visibility timeout configuration
&lt;/h2&gt;

&lt;p&gt;When SQS delivers a message to a consumer, the message becomes temporarily invisible to other consumers.&lt;/p&gt;

&lt;p&gt;This period is called the visibility timeout.&lt;/p&gt;

&lt;p&gt;If the consumer successfully processes and deletes the message, processing is complete.&lt;/p&gt;

&lt;p&gt;If the message is not deleted before the timeout expires, it becomes visible and can be processed again.&lt;/p&gt;

&lt;p&gt;This project configures:&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;VisibilityTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Lambda functions have:&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;Timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The visibility timeout is therefore significantly longer than the function timeout.&lt;/p&gt;

&lt;p&gt;AWS recommends setting the queue visibility timeout to at least six times the Lambda function timeout to allow time for retries if processing is throttled.&lt;/p&gt;

&lt;p&gt;A visibility timeout that is too short can cause the same message to become visible while the first consumer is still processing it.&lt;/p&gt;

&lt;p&gt;A timeout that is unnecessarily long can delay retries after genuine failures.&lt;/p&gt;

&lt;p&gt;It should be selected based on actual processing duration, batching configuration, throttling risk, and retry requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Long polling
&lt;/h2&gt;

&lt;p&gt;The queues use:&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;ReceiveMessageWaitTimeSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables long polling.&lt;/p&gt;

&lt;p&gt;Long polling allows SQS to wait briefly for a message instead of immediately returning an empty response. AWS documents that long polling can reduce empty and false-empty responses, with a maximum wait time of 20 seconds.&lt;/p&gt;

&lt;p&gt;Although Lambda manages polling when SQS is configured as an event source, setting appropriate queue parameters remains useful for queue behavior and for any additional consumers that may poll the queue directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure as Code with AWS SAM
&lt;/h2&gt;

&lt;p&gt;The entire solution is defined in one AWS SAM template.&lt;/p&gt;

&lt;p&gt;The template creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API Gateway routes&lt;/li&gt;
&lt;li&gt;Four Lambda functions&lt;/li&gt;
&lt;li&gt;One SNS topic&lt;/li&gt;
&lt;li&gt;Three primary SQS queues&lt;/li&gt;
&lt;li&gt;Three dead-letter queues&lt;/li&gt;
&lt;li&gt;Four DynamoDB tables&lt;/li&gt;
&lt;li&gt;SNS subscriptions&lt;/li&gt;
&lt;li&gt;Queue policies&lt;/li&gt;
&lt;li&gt;IAM permissions&lt;/li&gt;
&lt;li&gt;Lambda event source mappings&lt;/li&gt;
&lt;li&gt;CloudWatch alarms&lt;/li&gt;
&lt;li&gt;CloudFormation outputs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes the architecture repeatable.&lt;/p&gt;

&lt;p&gt;Instead of manually creating resources in the AWS Management Console, the environment can be deployed using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run &lt;span class="nb"&gt;install&lt;/span&gt;:all
sam validate &lt;span class="nt"&gt;--lint&lt;/span&gt;
sam build
sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SAM build packages each Lambda service and creates the deployment artifacts.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpp0jed8xau6lt01wl3vn.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpp0jed8xau6lt01wl3vn.png" alt="SAM build succeeded" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The guided deployment captures the stack name, AWS Region, environment parameter, IAM capability confirmation, and local SAM configuration file.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbvsmq1f86j38kokl65up.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbvsmq1f86j38kokl65up.png" alt="SAM guided deployment settings" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After CloudFormation completes, the stack outputs include the topic, queue, table, and API details created by the template.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fngm082fu4vpy6zvt0tl2.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fngm082fu4vpy6zvt0tl2.png" alt="SAM deployment completed" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The API URL can then be read from the CloudFormation stack outputs and used for test requests.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3pk7n022f9l4ptyc8f6a.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3pk7n022f9l4ptyc8f6a.png" alt="API URL from CloudFormation outputs" width="798" height="105"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Subsequent deployments use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam build
sam deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stack can be removed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam delete &lt;span class="nt"&gt;--stack-name&lt;/span&gt; sns-sqs-microservices
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Infrastructure as Code provides several practical advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consistent environments&lt;/li&gt;
&lt;li&gt;Version-controlled infrastructure&lt;/li&gt;
&lt;li&gt;Easier peer review&lt;/li&gt;
&lt;li&gt;Repeatable deployment&lt;/li&gt;
&lt;li&gt;Reduced configuration drift&lt;/li&gt;
&lt;li&gt;Easier disaster recovery&lt;/li&gt;
&lt;li&gt;A foundation for CI/CD automation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The deployed resources are visible across the AWS console. The Lambda functions represent the four services in the workflow.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1mh79oonvx6mmq7wd8hj.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1mh79oonvx6mmq7wd8hj.png" alt="Lambda functions deployed in AWS" width="799" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The DynamoDB tables give each service its own persistence boundary.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpr7osqv7cglfb6gte6w9.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpr7osqv7cglfb6gte6w9.png" alt="DynamoDB tables created by the stack" width="799" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CloudFormation keeps the whole environment grouped as one repeatable stack.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fin9xcne103snf0bf5wuo.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fin9xcne103snf0bf5wuo.png" alt="CloudFormation stack status" width="800" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the architecture
&lt;/h2&gt;

&lt;p&gt;A serious microservices project should test more than the successful path.&lt;/p&gt;

&lt;p&gt;This project contains three test scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 1: Successful order
&lt;/h3&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;"paymentOutcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"approved"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inventoryOutcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reserved"&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;Expected results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order stored
Payment approved
Inventory reserved
Payment notification recorded
Inventory notification recorded
No DLQ messages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The successful request returns a &lt;code&gt;202 Accepted&lt;/code&gt; response with the generated &lt;code&gt;orderId&lt;/code&gt;, &lt;code&gt;eventId&lt;/code&gt;, and &lt;code&gt;EVENT_PUBLISHED&lt;/code&gt; status.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvvy5rpg4keah3mf6v95r.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvvy5rpg4keah3mf6v95r.png" alt="Successful order API response" width="800" height="179"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 2: Business failure
&lt;/h3&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;"paymentOutcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"declined"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inventoryOutcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"out_of_stock"&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;Expected results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PaymentFailed published
InventoryFailed published
Failure notifications recorded
No DLQ messages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The services operated correctly. The business simply produced negative outcomes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 3: Technical failure
&lt;/h3&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;"simulateTechnicalFailure"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payment"&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;Expected behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payment processing throws an exception
The message becomes visible again
Lambda retries it
The receive count increases
The message moves to the Payment DLQ after three attempts
The Payment DLQ CloudWatch alarm enters ALARM state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Testing failure behavior is just as important as testing successful processing.&lt;/p&gt;

&lt;p&gt;A system is not resilient merely because it has a queue. Resilience depends on retry policy, idempotency, timeout selection, DLQ handling, monitoring, and recovery procedures.&lt;/p&gt;

&lt;h2&gt;
  
  
  The transactional outbox consideration
&lt;/h2&gt;

&lt;p&gt;The Order Service currently performs two operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Write the order to DynamoDB
2. Publish OrderCreated to SNS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These operations are not part of one atomic transaction.&lt;/p&gt;

&lt;p&gt;Consider this sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order write succeeds
Application crashes
SNS publication never happens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The order exists, but Payment and Inventory never receive the event.&lt;/p&gt;

&lt;p&gt;This is known as the dual-write problem.&lt;/p&gt;

&lt;p&gt;For a learning project, the current implementation records an EVENT_PUBLISH_FAILED state so the failure can be identified.&lt;/p&gt;

&lt;p&gt;For a production system, I would implement the transactional outbox pattern.&lt;/p&gt;

&lt;p&gt;A DynamoDB-based version could work as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store the order and an outbox event in one DynamoDB transaction.&lt;/li&gt;
&lt;li&gt;Enable DynamoDB Streams.&lt;/li&gt;
&lt;li&gt;Invoke an event publisher from the stream.&lt;/li&gt;
&lt;li&gt;Publish the outbox event to SNS.&lt;/li&gt;
&lt;li&gt;Record successful publication or allow the outbox item to expire.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The transactional outbox pattern addresses inconsistent outcomes when an application must update a database and publish an event as part of the same business operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Business benefits
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Better customer response times
&lt;/h3&gt;

&lt;p&gt;The API does not need to wait for every downstream service to finish.&lt;/p&gt;

&lt;p&gt;It can acknowledge that the order was accepted while processing continues asynchronously.&lt;/p&gt;

&lt;p&gt;This provides a more responsive customer experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Reduced blast radius
&lt;/h3&gt;

&lt;p&gt;If Notification fails, Payment and Inventory can continue operating.&lt;/p&gt;

&lt;p&gt;If Inventory experiences a backlog, Payment can still process messages.&lt;/p&gt;

&lt;p&gt;Failures are isolated to individual queues and consumers.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Independent scalability
&lt;/h3&gt;

&lt;p&gt;Each microservice scales according to its own workload.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payment Service: 100 concurrent workers
Inventory Service: 40 concurrent workers
Notification Service: 500 concurrent workers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The business does not need to scale the complete application just because one function is under heavy load.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Easier business expansion
&lt;/h3&gt;

&lt;p&gt;New consumers can subscribe to the event topic without changing the original publisher.&lt;/p&gt;

&lt;p&gt;A business can add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fraud detection&lt;/li&gt;
&lt;li&gt;Customer loyalty points&lt;/li&gt;
&lt;li&gt;Data analytics&lt;/li&gt;
&lt;li&gt;Shipping fulfilment&lt;/li&gt;
&lt;li&gt;Audit logging&lt;/li&gt;
&lt;li&gt;Recommendation engines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This reduces the cost and risk of adding new capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Traffic-spike protection
&lt;/h3&gt;

&lt;p&gt;Queues absorb sudden increases in traffic.&lt;/p&gt;

&lt;p&gt;This is particularly valuable for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flash sales&lt;/li&gt;
&lt;li&gt;Ticket releases&lt;/li&gt;
&lt;li&gt;Registration deadlines&lt;/li&gt;
&lt;li&gt;Payroll processing&lt;/li&gt;
&lt;li&gt;Month-end billing&lt;/li&gt;
&lt;li&gt;Marketing campaigns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The producer can accept work while consumers process the backlog at a controlled rate.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Improved operational visibility
&lt;/h3&gt;

&lt;p&gt;Each queue exposes useful CloudWatch metrics, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visible message count&lt;/li&gt;
&lt;li&gt;In-flight message count&lt;/li&gt;
&lt;li&gt;Age of the oldest message&lt;/li&gt;
&lt;li&gt;Number of messages received&lt;/li&gt;
&lt;li&gt;Number of messages deleted&lt;/li&gt;
&lt;li&gt;DLQ depth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These metrics help teams identify which part of a business workflow is slowing down.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Cost-efficient serverless operation
&lt;/h3&gt;

&lt;p&gt;The architecture uses managed services and does not require teams to operate message brokers or application servers.&lt;/p&gt;

&lt;p&gt;The business pays based largely on usage while AWS manages the underlying infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Stronger team autonomy
&lt;/h3&gt;

&lt;p&gt;Different teams can own different services.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Checkout Team -&amp;gt; Order Service
Finance Team -&amp;gt; Payment Service
Operations Team -&amp;gt; Inventory Service
Customer Experience Team -&amp;gt; Notification Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Teams can deploy and scale their services independently, provided they continue to respect the agreed event contracts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this architecture can be applied
&lt;/h2&gt;

&lt;h3&gt;
  
  
  E-commerce order processing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrderCreated
PaymentProcessed
InventoryReserved
ShipmentCreated
CustomerNotified
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the most direct application of the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Financial transaction processing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TransactionSubmitted
FraudCheckRequested
ComplianceCheckCompleted
LedgerUpdated
CustomerAlerted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additional controls are required for financial workloads, especially around exactly-once business effects, security, audit trails, and reconciliation.&lt;/p&gt;

&lt;h3&gt;
  
  
  User registration and onboarding
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UserRegistered
IdentityVerificationRequested
WelcomeEmailRequested
CRMProfileCreated
AnalyticsEventRecorded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The registration API can remain responsive while secondary activities happen asynchronously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Media-processing pipelines
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FileUploaded
VirusScanRequested
VideoTranscodingRequested
ThumbnailGenerationRequested
MetadataExtractionRequested
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each processing stage can have its own queue and worker capacity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Internet of Things workloads
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DeviceReadingReceived
AnomalyDetectionRequested
DataArchived
AlertGenerated
DashboardUpdated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Queues help absorb bursts from thousands of devices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logistics and fulfilment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PackageCreated
WarehouseAssigned
DriverRequested
TrackingUpdated
CustomerNotified
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Different services can react to fulfilment events without direct dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Insurance claims processing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ClaimSubmitted
DocumentValidationRequested
FraudAssessmentRequested
AdjusterAssigned
CustomerUpdated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Long-running workflows can be divided into independent processing stages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Healthcare administrative workflows
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AppointmentBooked
ReminderRequested
BillingRecordCreated
InsuranceCheckRequested
AuditRecordCreated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sensitive workloads require appropriate security, privacy, encryption, logging, and regulatory controls.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI/CD and DevOps automation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BuildCompleted
SecurityScanRequested
ArtifactPublished
DeploymentRequested
NotificationRequested
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SNS and SQS can decouple build, scanning, deployment, audit, and notification tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this architecture may not be appropriate
&lt;/h2&gt;

&lt;p&gt;Event-driven architecture should not be adopted simply because it is modern.&lt;/p&gt;

&lt;p&gt;It introduces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Eventual consistency&lt;/li&gt;
&lt;li&gt;More infrastructure components&lt;/li&gt;
&lt;li&gt;More complex debugging&lt;/li&gt;
&lt;li&gt;Schema-management requirements&lt;/li&gt;
&lt;li&gt;Duplicate-delivery considerations&lt;/li&gt;
&lt;li&gt;Distributed tracing challenges&lt;/li&gt;
&lt;li&gt;More demanding operational procedures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple synchronous request may be better when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The client requires an immediate final result.&lt;/li&gt;
&lt;li&gt;The workflow contains only one fast and reliable dependency.&lt;/li&gt;
&lt;li&gt;The application is small and unlikely to expand.&lt;/li&gt;
&lt;li&gt;Eventual consistency is unacceptable.&lt;/li&gt;
&lt;li&gt;The team lacks the operational maturity to manage distributed systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Architecture should solve a genuine business or engineering problem, not merely increase the number of AWS services in a diagram.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key lessons from the project
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Lesson 1: Microservices are not automatically decoupled
&lt;/h3&gt;

&lt;p&gt;Breaking a monolith into multiple HTTP services does not remove coupling.&lt;/p&gt;

&lt;p&gt;If every service synchronously depends on the next service, the system remains operationally coupled.&lt;/p&gt;

&lt;p&gt;True decoupling requires careful control of dependencies, contracts, data ownership, and failure behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 2: Give every consumer its own queue
&lt;/h3&gt;

&lt;p&gt;Payment and Inventory should not compete for messages from one shared queue.&lt;/p&gt;

&lt;p&gt;With one queue per microservice, each interested service receives its own copy and controls its own retries, backlog, scaling, and DLQ.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 3: Events should describe facts
&lt;/h3&gt;

&lt;p&gt;Good event names describe something that has happened:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrderCreated
PaymentCompleted
PaymentFailed
InventoryReserved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avoid vague names such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ProcessData
HandleRequest
RunService
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Business-oriented event names make architectures easier to understand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 4: Business failures are not technical failures
&lt;/h3&gt;

&lt;p&gt;A declined payment is not a processing exception.&lt;/p&gt;

&lt;p&gt;It is a valid business outcome and should be published as an event.&lt;/p&gt;

&lt;p&gt;Sending every negative outcome to a DLQ makes operations noisy and removes valuable business meaning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 5: At-least-once delivery requires idempotency
&lt;/h3&gt;

&lt;p&gt;Duplicate delivery is not an unusual edge case that can be ignored.&lt;/p&gt;

&lt;p&gt;Every consumer that performs a side effect must define how duplicates are detected and handled.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 6: DLQs need ownership
&lt;/h3&gt;

&lt;p&gt;Creating a DLQ is not enough.&lt;/p&gt;

&lt;p&gt;Teams need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alerts&lt;/li&gt;
&lt;li&gt;Runbooks&lt;/li&gt;
&lt;li&gt;Dashboards&lt;/li&gt;
&lt;li&gt;Investigation procedures&lt;/li&gt;
&lt;li&gt;Redrive procedures&lt;/li&gt;
&lt;li&gt;Message-retention policies&lt;/li&gt;
&lt;li&gt;Service ownership&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Lesson 7: Event schemas need governance
&lt;/h3&gt;

&lt;p&gt;An event is a contract.&lt;/p&gt;

&lt;p&gt;Production systems should define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schema ownership&lt;/li&gt;
&lt;li&gt;Event versioning&lt;/li&gt;
&lt;li&gt;Required and optional fields&lt;/li&gt;
&lt;li&gt;Compatibility rules&lt;/li&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;Deprecation procedures&lt;/li&gt;
&lt;li&gt;Consumer contract tests&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Lesson 8: Observability must cross service boundaries
&lt;/h3&gt;

&lt;p&gt;A single request can generate several events across several services.&lt;/p&gt;

&lt;p&gt;Use fields such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eventId
correlationId
orderId
source
eventType
occurredAt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These values should appear consistently in structured logs so teams can trace a workflow from beginning to end.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 9: Design failure paths before production
&lt;/h3&gt;

&lt;p&gt;Successful requests are usually the easiest part of the system.&lt;/p&gt;

&lt;p&gt;The difficult questions are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What happens when publication fails?
What happens when a consumer times out?
What happens when the same event arrives twice?
What happens when a DLQ begins growing?
How is a failed event replayed safely?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These questions should be answered during architecture design, not after an incident.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 10: Start simple, then harden deliberately
&lt;/h3&gt;

&lt;p&gt;This project is intentionally understandable.&lt;/p&gt;

&lt;p&gt;A production version can later add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon Cognito or another identity provider&lt;/li&gt;
&lt;li&gt;AWS WAF&lt;/li&gt;
&lt;li&gt;AWS KMS customer-managed keys&lt;/li&gt;
&lt;li&gt;AWS Secrets Manager&lt;/li&gt;
&lt;li&gt;Amazon SES&lt;/li&gt;
&lt;li&gt;AWS X-Ray tracing&lt;/li&gt;
&lt;li&gt;CloudWatch dashboards&lt;/li&gt;
&lt;li&gt;AWS Lambda Powertools&lt;/li&gt;
&lt;li&gt;Event schema validation&lt;/li&gt;
&lt;li&gt;Transactional outbox&lt;/li&gt;
&lt;li&gt;CI/CD with GitHub Actions and AWS OIDC&lt;/li&gt;
&lt;li&gt;Multi-account environments&lt;/li&gt;
&lt;li&gt;Reserved or maximum concurrency&lt;/li&gt;
&lt;li&gt;Replay and redrive automation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Starting with a clear foundation is better than introducing every possible service before the application has demonstrated the need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The primary value of this project is not simply that it uses Amazon SNS and Amazon SQS.&lt;/p&gt;

&lt;p&gt;Its value is that it demonstrates several fundamental distributed-system principles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Producers should not need to know every consumer.
Consumers should own their queues.
Failures should be isolated.
Messages should be retried safely.
Duplicate delivery should not create duplicate business effects.
Business outcomes should be represented as events.
Operations teams should be able to detect and recover failed work.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Amazon SNS handles event distribution.&lt;/p&gt;

&lt;p&gt;Amazon SQS provides durability, buffering, retry isolation, and independent consumer control.&lt;/p&gt;

&lt;p&gt;AWS Lambda provides serverless processing.&lt;/p&gt;

&lt;p&gt;DynamoDB provides service-owned persistence.&lt;/p&gt;

&lt;p&gt;AWS SAM makes the complete environment repeatable through Infrastructure as Code.&lt;/p&gt;

&lt;p&gt;Together, these services create an architecture that can respond quickly to customers, absorb traffic spikes, isolate failures, scale individual workloads, and support new business capabilities without repeatedly rewriting the original Order Service.&lt;/p&gt;

&lt;p&gt;That is the real purpose of decoupling: not to make the architecture diagram more impressive, but to make the business more adaptable and the system more resilient.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>microservices</category>
      <category>aws</category>
    </item>
    <item>
      <title>Deploying Infrastructure to Azure Using HCP Terraform and GitHub VCS</title>
      <dc:creator>Bernard Chika Uwaezuoke</dc:creator>
      <pubDate>Thu, 04 Jun 2026 16:42:00 +0000</pubDate>
      <link>https://dev.to/donhadley22/deploying-infrastructure-to-azure-using-hcp-terraform-and-github-vcs-44pk</link>
      <guid>https://dev.to/donhadley22/deploying-infrastructure-to-azure-using-hcp-terraform-and-github-vcs-44pk</guid>
      <description>&lt;h2&gt;
  
  
  INTRODUCTION
&lt;/h2&gt;

&lt;p&gt;Every Cloud Engineer eventually reaches the same point.&lt;br&gt;
At first, running Terraform from your laptop feels exciting. You write your .tf files, run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform init&lt;br&gt;
terraform plan&lt;br&gt;
terraform apply&lt;/code&gt;&lt;br&gt;
and Azure resources appear like magic.&lt;/p&gt;

&lt;p&gt;But as projects grow, that approach becomes risky.&lt;br&gt;
What happens when another engineer needs to review your changes?&lt;br&gt;
What happens when the state file is sitting on someone’s laptop?&lt;br&gt;
What happens when a junior engineer accidentally applies changes to production without approval?&lt;/p&gt;

&lt;p&gt;That is where HCP Terraform and GitHub VCS integration come in.&lt;br&gt;
In this project, I will walk you through how to deploy Azure infrastructure using Terraform code stored in GitHub, with HCP Terraform handling remote runs, state management, plan review, and controlled apply operations.&lt;/p&gt;

&lt;p&gt;As a Cloud Engineer and technical instructor, this is the kind of workflow I encourage students and teams to learn early because it reflects how real infrastructure should be managed: &lt;strong&gt;version-controlled,&lt;/strong&gt; &lt;strong&gt;reviewable&lt;/strong&gt;, &lt;strong&gt;auditable&lt;/strong&gt;, and &lt;strong&gt;repeatable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;HCP Terraform supports a VCS-driven workflow where it fetches Terraform configuration from a connected repository and automatically starts plan and apply operations when changes are pushed. This allows GitHub to act as the source of truth for infrastructure code. &lt;/p&gt;
&lt;h2&gt;
  
  
  Project Goal
&lt;/h2&gt;
&lt;h3&gt;
  
  
  We are going to deploy basic Azure infrastructure using:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Azure as the cloud provider &lt;/li&gt;
&lt;li&gt;Terraform as the Infrastructure as Code tool &lt;/li&gt;
&lt;li&gt;GitHub as the version control system &lt;/li&gt;
&lt;li&gt;HCP Terraform as the remote execution and state management platform &lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  The deployment will create:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Azure Resource Group &lt;/li&gt;
&lt;li&gt;Virtual Network &lt;/li&gt;
&lt;li&gt;Subnet &lt;/li&gt;
&lt;li&gt;Network Security Group &lt;/li&gt;
&lt;li&gt;Public IP &lt;/li&gt;
&lt;li&gt;Network Interface &lt;/li&gt;
&lt;li&gt;Linux Virtual Machine &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&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%2Fwntxpexlu4qt6hzh7wfi.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%2Fwntxpexlu4qt6hzh7wfi.png" alt="Architectural Overview" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  In simple terms:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;We write Terraform code locally. &lt;/li&gt;
&lt;li&gt;We push the code to GitHub. &lt;/li&gt;
&lt;li&gt;HCP Terraform detects the change. &lt;/li&gt;
&lt;li&gt;HCP Terraform runs terraform plan. &lt;/li&gt;
&lt;li&gt;After approval, HCP Terraform runs terraform apply. &lt;/li&gt;
&lt;li&gt;Azure resources are deployed. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a professional DevOps workflow because no one is manually creating resources in the Azure portal, and no one is running production infrastructure from their laptop (Minimum DevOps requirements).&lt;/p&gt;
&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Before starting, make sure you have:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;An Azure subscription &lt;/li&gt;
&lt;li&gt;A GitHub account &lt;/li&gt;
&lt;li&gt;An HCP Terraform account &lt;/li&gt;
&lt;li&gt;Terraform installed locally &lt;/li&gt;
&lt;li&gt;Azure CLI installed locally &lt;/li&gt;
&lt;li&gt;Basic knowledge of Git and Terraform&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You also need permission to create resources in Azure and permission to create or use a service principal &lt;a href="https://learn.microsoft.com/en-us/azure/role-based-access-control/quickstart-assign-role-user-portal" rel="noopener noreferrer"&gt;click here to know more&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Here We Go!&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Create a GitHub Repository
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create a new GitHub repository.&lt;br&gt;
&lt;code&gt;azure-hcp-terraform-vcs&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clone the repository locally&lt;br&gt;
&lt;code&gt;git clone https://github.com/YOUR_USERNAME/azure-hcp-terraform-vcs.git&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Switch to the cloned repository&lt;br&gt;
&lt;code&gt;cd azure-hcp-terraform-vcs&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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%2Ftc7xc7ueuhq51r55aia3.png" alt="Create a repo" width="388" height="485"&gt;
  &lt;center&gt;Creating a Repo in GitHub&lt;/center&gt;


&lt;p&gt;For details on how to create a GitHub repository, &lt;a href="https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository" rel="noopener noreferrer"&gt;click here&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create the Terraform project structure
azure-hcp-terraform-vcs/
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tf&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tf&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tf&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tf&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="k"&gt;terraform&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tfvars&lt;/span&gt;
&lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="nx"&gt;README&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;md&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%2Fupoieomg12lf1fn5watt.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%2Fupoieomg12lf1fn5watt.png" alt="Project Structure" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above structure is simple enough for beginners to understand, but also professional enough to build on later.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Configure the Azure Provider
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Create providers.tf.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.6.0"&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;azurerm&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/azurerm"&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; 4.0"&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="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"azurerm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;features&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;This tells Terraform to use the AzureRM provider.&lt;br&gt;
The &lt;code&gt;features {}&lt;/code&gt; block is required by the AzureRM provider.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3: Create Terraform Variables
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Create variables.tf.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"resource_group_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 the Azure Resource Group"&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="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"location"&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;"Azure region where resources will be deployed"&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="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"vnet_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 the Virtual Network"&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="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"vnet_address_space"&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;"Address space for the Virtual Network"&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"subnet_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 the subnet"&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="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"subnet_address_prefixes"&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;"Address prefixes for the subnet"&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"vm_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 the Linux Virtual Machine"&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="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"admin_username"&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;"Admin username for the virtual machine"&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="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"public_key"&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;"SSH public key content for the virtual machine"&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;
  
  
  Important  Note
&lt;/h3&gt;

&lt;p&gt;In HCP Terraform, avoid using local file paths like this:&lt;br&gt;
&lt;code&gt;file("C:/Users/USER/.ssh/azure_rsa.pub")&lt;/code&gt;&lt;br&gt;
That path exists on your laptop, not inside the HCP Terraform remote execution environment.&lt;br&gt;
Instead, paste the actual public key content into an HCP Terraform variable and check the &lt;strong&gt;sensitive&lt;/strong&gt; atribute box.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4: Create the Azure Infrastructure Code
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Create main.tf.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"rg"&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="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_group_name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_virtual_network"&lt;/span&gt; &lt;span class="s2"&gt;"vnet"&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="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vnet_name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&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;address_space&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vnet_address_space&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"subnet"&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="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnet_name&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&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;virtual_network_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_virtual_network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vnet&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;address_prefixes&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnet_address_prefixes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_network_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"nsg"&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;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vm_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-nsg"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&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;security_rule&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;"Allow-SSH"&lt;/span&gt;
    &lt;span class="nx"&gt;priority&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1001&lt;/span&gt;
    &lt;span class="nx"&gt;direction&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Inbound"&lt;/span&gt;
    &lt;span class="nx"&gt;access&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;protocol&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;source_port_range&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt;
    &lt;span class="nx"&gt;destination_port_range&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"22"&lt;/span&gt;
    &lt;span class="nx"&gt;source_address_prefix&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt;
    &lt;span class="nx"&gt;destination_address_prefix&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="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_public_ip"&lt;/span&gt; &lt;span class="s2"&gt;"public_ip"&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;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vm_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-public-ip"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&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;allocation_method&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Static"&lt;/span&gt;
  &lt;span class="nx"&gt;sku&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Standard"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_network_interface"&lt;/span&gt; &lt;span class="s2"&gt;"nic"&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;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vm_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-nic"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&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;ip_configuration&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;"internal"&lt;/span&gt;
    &lt;span class="nx"&gt;subnet_id&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnet&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;private_ip_address_allocation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Dynamic"&lt;/span&gt;
    &lt;span class="nx"&gt;public_ip_address_id&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_public_ip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_ip&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_network_interface_security_group_association"&lt;/span&gt; &lt;span class="s2"&gt;"nsg_association"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;network_interface_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_network_interface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nic&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;network_security_group_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_network_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nsg&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="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_linux_virtual_machine"&lt;/span&gt; &lt;span class="s2"&gt;"vm"&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="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vm_name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&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;size&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Standard_D2s_v3"&lt;/span&gt;
  &lt;span class="nx"&gt;admin_username&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;admin_username&lt;/span&gt;

  &lt;span class="nx"&gt;network_interface_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;azurerm_network_interface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nic&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;admin_ssh_key&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;username&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;admin_username&lt;/span&gt;
    &lt;span class="nx"&gt;public_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_key&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;os_disk&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;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vm_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-osdisk"&lt;/span&gt;
    &lt;span class="nx"&gt;caching&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ReadWrite"&lt;/span&gt;
    &lt;span class="nx"&gt;storage_account_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Standard_LRS"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;source_image_reference&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;publisher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Canonical"&lt;/span&gt;
    &lt;span class="nx"&gt;offer&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu-24_04-lts"&lt;/span&gt;
    &lt;span class="nx"&gt;sku&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"server"&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;"latest"&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;This code builds a simple Azure VM environment with networking, security, and SSH access.&lt;br&gt;
This is a good starter project because it touches the core Azure infrastructure components: &lt;strong&gt;compute&lt;/strong&gt;, &lt;strong&gt;networking&lt;/strong&gt;, &lt;strong&gt;identity&lt;/strong&gt;, and &lt;strong&gt;security&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5: Create Outputs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Create outputs.tf.
&lt;/li&gt;
&lt;/ul&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;"resource_group_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 created resource group"&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;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&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;output&lt;/span&gt; &lt;span class="s2"&gt;"public_ip_address"&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 public IP address of the virtual machine"&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;azurerm_public_ip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_ip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ip_address&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;"vm_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 virtual machine"&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;azurerm_linux_virtual_machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vm&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Outputs are useful because after deployment, HCP Terraform will show important values such as the public IP address of the VM.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 6: Create an Example tfvars File
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Create terraform.tfvars
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource_group_name&lt;/span&gt;      &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-hcp-terraform-demo"&lt;/span&gt;
&lt;span class="nx"&gt;location&lt;/span&gt;                 &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"westeurope"&lt;/span&gt;
&lt;span class="nx"&gt;vnet_name&lt;/span&gt;                &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vnet-hcp-demo"&lt;/span&gt;
&lt;span class="nx"&gt;vnet_address_space&lt;/span&gt;       &lt;span class="err"&gt;=&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="p"&gt;]&lt;/span&gt;
&lt;span class="nx"&gt;subnet_name&lt;/span&gt;              &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"subnet-hcp-demo"&lt;/span&gt;
&lt;span class="nx"&gt;subnet_address_prefixes&lt;/span&gt;  &lt;span class="err"&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="nx"&gt;vm_name&lt;/span&gt;                  &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vm-hcp-demo"&lt;/span&gt;
&lt;span class="nx"&gt;admin_username&lt;/span&gt;           &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"azureuser"&lt;/span&gt;
&lt;span class="nx"&gt;public_key&lt;/span&gt;               &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ssh-rsa AAAA..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Do not commit real secrets or sensitive values into GitHub.&lt;br&gt;
For public_key, use the content of your .pub file.&lt;br&gt;
&lt;strong&gt;For example:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;cat ~/.ssh/azure_rsa.pub&lt;/code&gt;&lt;br&gt;
On Windows PowerShell:&lt;br&gt;
&lt;code&gt;Get-Content C:\Users\USER\.ssh\azure_rsa.pub&lt;/code&gt;&lt;br&gt;
Copy the full public key content.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 7: Create an Azure Service Principal
&lt;/h2&gt;

&lt;p&gt;HCP Terraform needs permission to deploy resources into Azure.&lt;br&gt;
The common approach is to create a service principal and store its values as environment variables in HCP Terraform. Microsoft recommends using service principals for automated tools such as Terraform instead of using a fully privileged personal user account. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Login to Azure:&lt;br&gt;
&lt;code&gt;az login&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confirm your subscription:&lt;br&gt;
&lt;code&gt;az account show&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set the subscription:&lt;br&gt;
&lt;code&gt;az account set --subscription "&amp;lt;SUBSCRIPTION_ID&amp;gt;"&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a service principal:&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;az ad sp create-for-rbac &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"sp-hcp-terraform-azure"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role&lt;/span&gt; &lt;span class="s2"&gt;"Contributor"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scopes&lt;/span&gt; &lt;span class="s2"&gt;"/subscriptions/&amp;lt;SUBSCRIPTION_ID&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The output will look similar to this:&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;"appId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"displayName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sp-hcp-terraform-azure"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxxxxxxxxxxxxxxxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tenant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"&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;Save these values securely.&lt;br&gt;
You will need them in HCP Terraform.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 8: Push Your Code to GitHub
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;In your working director, run:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
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;"Initial Azure infrastructure with Terraform"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;At this point, GitHub becomes the source of truth for your infrastructure code.&lt;br&gt;
This is a major shift from manual cloud administration to real Infrastructure as Code practice.&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%2F62ovhfn1razwieq6o5nr.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%2F62ovhfn1razwieq6o5nr.png" alt="Push to GitHub" width="781" height="692"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The files in the repository in GitHub
&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%2Fx065inrrn36wmh8cmh0c.png" alt="Terraform Config Files in GitHub" width="800" height="533"&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Step 9: Create an HCP Terraform Organization
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to HCP Terraform and create an organization. &lt;a href="https://app.terraform.io/app" rel="noopener noreferrer"&gt;Click here&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on &lt;code&gt;Create Organization&lt;/code&gt;&lt;br&gt;
Select &lt;code&gt;Personal&lt;/code&gt;&lt;br&gt;
A good organization naming pattern could be:&lt;br&gt;
&lt;code&gt;peabsmart-cloud-labs&lt;/code&gt;&lt;/p&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%2F7or6kcnc8f2to4i635wn.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%2F7or6kcnc8f2to4i635wn.png" alt="HCP Organization" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On the left pane inside the organization click on &lt;code&gt;Projects&lt;/code&gt;,
then click on &lt;code&gt;New project&lt;/code&gt; and enter the project detail and Click on &lt;code&gt;Create&lt;/code&gt;.
For this project, we will use  &lt;code&gt;Azure-Training-Projects&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%2F7ne291gxz2se80o9qtjy.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%2F7ne291gxz2se80o9qtjy.png" alt="Create a Project" width="539" height="565"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Projects in HCP Terraform help organize related workspaces, especially when you are managing multiple environments or cloud providers. HashiCorp’s HCP Terraform learning path includes project and variable set organization as part of the recommended collaboration workflow. &lt;/p&gt;
&lt;h2&gt;
  
  
  Step 10: Connect HCP Terraform to GitHub and Create a HCP Terraform Workspace
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;In the project you created, Click on &lt;code&gt;Create a workspace&lt;/code&gt; HCP Terraform:&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%2Fuezlbsqa6c87e7nn48gv.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%2Fuezlbsqa6c87e7nn48gv.png" alt="Create a workspace" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Select &lt;code&gt;Version Control Workflow&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose GitHub. &lt;/p&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%2F2afx7no978l8cwqdoawx.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%2F2afx7no978l8cwqdoawx.png" alt="Connect GitHub" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Authorize HCP Terraform to access your GitHub account or organization. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select the repository: &lt;code&gt;azure-hcp-terraform-vcs&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click on &lt;code&gt;Create&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Terraform working directory: &lt;code&gt;/&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Use &lt;code&gt;/&lt;/code&gt; if your .tf files are in the root of the repository.&lt;br&gt;
If your files are inside a folder like&lt;code&gt;terraform/&lt;/code&gt;, then set the working directory to: &lt;code&gt;terraform&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a common mistake. If the working directory is wrong, HCP Terraform will not find your Terraform configuration.&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%2F5o336i4mazyljzh8o7k1.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%2F5o336i4mazyljzh8o7k1.png" alt="Complete Workspace" width="799" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The Terraform variable page will popup&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter the required variable values from terraform.tfvars file we created earlier and click &lt;code&gt;Save variables&lt;/code&gt;.&lt;/p&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%2Fd3w53qfinsck0o1u3baf.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%2Fd3w53qfinsck0o1u3baf.png" alt="Variables" width="712" height="513"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 11: Add Azure Credentials to HCP Terraform
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to the workspace:&lt;br&gt;
Workspace &amp;gt; Variables&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the following as Environment Variables:&lt;br&gt;
&lt;code&gt;ARM_CLIENT_ID&lt;br&gt;
ARM_CLIENT_SECRET&lt;br&gt;
ARM_SUBSCRIPTION_ID&lt;br&gt;
ARM_TENANT_ID&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use these values:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;ARM_CLIENT_ID&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;appId from the service principal output&lt;/span&gt;
&lt;span class="py"&gt;ARM_CLIENT_SECRET&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;password from the service principal output&lt;/span&gt;
&lt;span class="py"&gt;ARM_SUBSCRIPTION_ID&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;your Azure subscription ID&lt;/span&gt;
&lt;span class="py"&gt;ARM_TENANT_ID&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;tenant from the service principal output&lt;/span&gt;
&lt;span class="err"&gt;Mark&lt;/span&gt; &lt;span class="err"&gt;ARM_CLIENT_SECRET&lt;/span&gt; &lt;span class="err"&gt;as&lt;/span&gt; &lt;span class="err"&gt;Sensitive.&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%2Fbvq5eocpwch5wq5pqgzi.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%2Fbvq5eocpwch5wq5pqgzi.png" alt="Env Variables" width="800" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Microsoft documents these Azure Terraform authentication variables as the standard service-principal environment variables for Terraform automation. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 12: Queue the First Terraform Plan
&lt;/h2&gt;

&lt;p&gt;Once the workspace is connected to GitHub and variables are added, HCP Terraform will usually trigger a run automatically.&lt;/p&gt;

&lt;p&gt;You can also manually queue a run:&lt;br&gt;
Workspace &amp;gt; Runs &amp;gt; Queue plan&lt;br&gt;
HCP Terraform will run:&lt;br&gt;
terraform init&lt;br&gt;
terraform plan&lt;br&gt;
remotely.&lt;br&gt;
This is where the value becomes clear.&lt;br&gt;
The plan is no longer hidden on one person’s machine. It is visible, reviewable, and part of the deployment workflow.&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%2F4fb9vim0sqt5cvj3ul0r.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%2F4fb9vim0sqt5cvj3ul0r.png" alt="runs" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 13: Review the Plan
&lt;/h2&gt;

&lt;p&gt;A good Cloud Engineer does not blindly apply infrastructure changes.&lt;br&gt;
Review the plan carefully.&lt;br&gt;
&lt;strong&gt;Look for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resources to be created &lt;/li&gt;
&lt;li&gt;Resources to be modified &lt;/li&gt;
&lt;li&gt;Resources to be destroyed &lt;/li&gt;
&lt;li&gt;Unexpected region changes &lt;/li&gt;
&lt;li&gt;Unexpected VM sizes &lt;/li&gt;
&lt;li&gt;Public exposure &lt;/li&gt;
&lt;li&gt;Security group rules &lt;/li&gt;
&lt;li&gt;Naming mistakes &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if the plan says:&lt;br&gt;
Plan: 8 to add, 0 to change, 0 to destroy.&lt;br&gt;
that means Terraform intends to create 8 resources.&lt;br&gt;
A depicted in the image below&lt;br&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%2F5ncqkwbyo0g1e7bepen5.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%2F5ncqkwbyo0g1e7bepen5.png" alt=" " width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 14: Apply the Infrastructure
&lt;/h2&gt;

&lt;p&gt;If the plan looks correct, click: &lt;code&gt;Confirm &amp;amp; Apply&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%2Fvbt3nw2tmcwi64fg9srz.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%2Fvbt3nw2tmcwi64fg9srz.png" alt="Apply" width="800" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;HCP Terraform will run the apply operation remotely.&lt;br&gt;
After a successful apply, you should see your outputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;rg-hcp-terraform-demo&lt;/span&gt;
&lt;span class="py"&gt;vm_name&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;vm-hcp-demo&lt;/span&gt;
&lt;span class="py"&gt;public_ip_address&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;x.x.x.x&lt;/span&gt;
&lt;span class="err"&gt;You&lt;/span&gt; &lt;span class="err"&gt;can&lt;/span&gt; &lt;span class="err"&gt;now&lt;/span&gt; &lt;span class="err"&gt;test&lt;/span&gt; &lt;span class="err"&gt;SSH&lt;/span&gt; &lt;span class="py"&gt;access&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="err"&gt;ssh&lt;/span&gt; &lt;span class="err"&gt;azureuser@&amp;lt;PUBLIC_IP_ADDRESS&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Apply completed &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%2Fjhprj1b1ikcd0ern7edu.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%2Fjhprj1b1ikcd0ern7edu.png" alt="Apply completed" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prove of authorization after review (Reviewer's Comment)&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%2Fwlapbsotksksgof3oqh3.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%2Fwlapbsotksksgof3oqh3.png" alt="Reviewers" width="704" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 15: Verify Resources in Azure
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to the Azure portal and check:&lt;br&gt;
Resource Groups &amp;gt; rg-hcp-terraform-demo&lt;br&gt;
You should see:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Virtual Machine &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Virtual Network &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Subnet &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Network Security Group &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Public IP &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Network Interface &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;OS Disk &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As Depicted 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%2F5f72akg8xxakxw1g0okz.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%2F5f72akg8xxakxw1g0okz.png" alt="Azure Portal" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also verify using Azure CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az resource list &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; rg-hcp-terraform-demo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 16: Make a Change Through GitHub
&lt;/h2&gt;

&lt;p&gt;Imagine your team wants to change the VM size.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Update this line in main.tf:&lt;br&gt;
&lt;code&gt;size = "Standard_D2s_v3"&lt;/code&gt;&lt;br&gt;
Change it to:&lt;br&gt;
&lt;code&gt;size = "Standard_B1s"&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Commit and push:&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;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;"Update VM size"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HCP Terraform detects the GitHub change and starts a new run.&lt;br&gt;
This is the power of VCS-driven infrastructure.&lt;br&gt;
You are no longer clicking around in Azure. You are managing infrastructure through code, Git history, and controlled deployment runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 17: Destroy the Infrastructure Safely
&lt;/h2&gt;

&lt;p&gt;When you are done with the demo, destroy the resources to avoid unnecessary Azure cost.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In HCP Terraform:
&lt;code&gt;Workspace &amp;gt; Settings &amp;gt; Destruction and Deletion &amp;gt; Queue destroy plan&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%2Fbk2upkxl7z8c3v16ilt5.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%2Fbk2upkxl7z8c3v16ilt5.png" alt="Queue destroy plan" width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Confirm by typing &lt;code&gt;delete&lt;/code&gt;&lt;br&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%2F55yht17lr20d7i6sov0l.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%2F55yht17lr20d7i6sov0l.png" alt="delete" width="602" height="335"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Review the destroy plan, then confirm.&lt;/p&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%2Fmgr88imt1ln3evul4mxc.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%2Fmgr88imt1ln3evul4mxc.png" alt="Confirm destroy" width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deletion Completed
&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%2F58l22wn1sb8ny89nrglt.png" alt="Deletion completed" width="800" height="336"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HCP Terraform supports destroy operations from the workspace, and HashiCorp warns that deleting a workspace does not automatically destroy the infrastructure it manages. You must queue a destroy plan if you want the actual cloud resources removed. &lt;/p&gt;

&lt;h2&gt;
  
  
  Common Errors and How to Fix Them
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Error 1: Unsupported Operator for public_key_path
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Example error:&lt;/strong&gt;&lt;br&gt;
invalid HCL for variable "public_key_path" at 1,1: Unsupported operator&lt;br&gt;
&lt;strong&gt;Cause:&lt;/strong&gt;&lt;br&gt;
You are probably passing a Windows path like this directly into HCP Terraform:&lt;br&gt;
C:/Users/USER/.ssh/azure_rsa.pub&lt;br&gt;
HCP Terraform runs remotely and cannot access files on your local machine.&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt;&lt;br&gt;
Use the actual public key content instead:&lt;br&gt;
public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ..."&lt;/p&gt;

&lt;h3&gt;
  
  
  Error 2: List of String Required, But Have String
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Bad value:&lt;/strong&gt;&lt;br&gt;
vnet_address_space = "[\"10.0.0.0/16\"]"&lt;br&gt;
&lt;strong&gt;Correct value:&lt;/strong&gt;&lt;br&gt;
vnet_address_space = ["10.0.0.0/16"]&lt;br&gt;
&lt;strong&gt;Cause:&lt;/strong&gt;&lt;br&gt;
The first example is a string. The second example is a real HCL list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error 3: Azure Authentication Failed
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Check that these environment variables exist in HCP Terraform:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;ARM_CLIENT_ID&lt;br&gt;
ARM_CLIENT_SECRET&lt;br&gt;
ARM_SUBSCRIPTION_ID&lt;br&gt;
ARM_TENANT_ID&lt;/code&gt;&lt;br&gt;
Also confirm that the service principal has enough permissions on the subscription or resource group.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error 4: Terraform Cannot Find Configuration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt;&lt;br&gt;
Your HCP Terraform workspace working directory is wrong.&lt;br&gt;
If your files are here:&lt;br&gt;
&lt;code&gt;repo/main.tf&lt;/code&gt;&lt;br&gt;
use:&lt;br&gt;
&lt;code&gt;/&lt;/code&gt;&lt;br&gt;
If your files are here:&lt;br&gt;
&lt;code&gt;repo/terraform/main.tf&lt;/code&gt;&lt;br&gt;
use:&lt;br&gt;
&lt;code&gt;terraform&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices I Teach My Students
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Do Not Store Secrets in GitHub
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Never commit:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;client secrets&lt;/li&gt;
&lt;li&gt;passwords&lt;/li&gt;
&lt;li&gt;private keys&lt;/li&gt;
&lt;li&gt;.tfstate files
Use HCP Terraform sensitive variables for secrets.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Use Git Branches and Pull Requests
&lt;/h3&gt;

&lt;p&gt;For production teams, avoid pushing directly to main.&lt;br&gt;
&lt;strong&gt;Use:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;feature branch &amp;gt; pull request &amp;gt; review &amp;gt; merge &amp;gt; HCP Terraform run&lt;/code&gt;&lt;br&gt;
This improves collaboration and prevents careless infrastructure changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Keep State Remote
&lt;/h3&gt;

&lt;p&gt;One of the strongest benefits of HCP Terraform is remote state management.&lt;br&gt;
Your state is not scattered across laptops.&lt;br&gt;
It is centrally managed, protected, and connected to the workspace.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Separate Environments
&lt;/h3&gt;

&lt;p&gt;For serious projects, create separate workspaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;azure-dev&lt;/li&gt;
&lt;li&gt;azure-staging&lt;/li&gt;
&lt;li&gt;azure-prod
Each workspace should have its own variables and approval controls.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Use Clear Naming Standards
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Bad names:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;test-rg&lt;/li&gt;
&lt;li&gt;vm1&lt;/li&gt;
&lt;li&gt;network-demo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Better names:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rg-dev-hcp-demo-weu&lt;/li&gt;
&lt;li&gt;vnet-dev-hcp-demo-weu&lt;/li&gt;
&lt;li&gt;vm-dev-hcp-demo-01
Good naming helps with operations, cost tracking, troubleshooting, and governance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Reflection
&lt;/h2&gt;

&lt;p&gt;This project is more than just deploying a virtual machine to Azure.&lt;br&gt;
It demonstrates a professional Infrastructure as Code workflow.&lt;br&gt;
As a Cloud Engineer, I see Terraform as more than a provisioning tool. It is a discipline. It teaches structure, repeatability, version control, collaboration, and accountability.&lt;/p&gt;

&lt;p&gt;As a technical instructor, I also see this workflow as one of the best ways to help students understand how modern cloud teams work. They learn that cloud engineering is not about clicking buttons in the portal. It is about building systems that are predictable, secure, reviewable, and easy to reproduce.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>github</category>
      <category>azure</category>
      <category>devops</category>
    </item>
    <item>
      <title>A Real-World Serverless Appointment Booking Backend on AWS</title>
      <dc:creator>Bernard Chika Uwaezuoke</dc:creator>
      <pubDate>Mon, 12 Jan 2026 23:02:56 +0000</pubDate>
      <link>https://dev.to/donhadley22/a-real-world-serverless-appointment-booking-backend-on-aws-dc4</link>
      <guid>https://dev.to/donhadley22/a-real-world-serverless-appointment-booking-backend-on-aws-dc4</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;strong&gt;From “Hello World” to a Real-World, Production-Ready System&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction: From Demo to Impact.
&lt;/h2&gt;

&lt;p&gt;Most tutorials stop at &lt;em&gt;“Hello from Serverless API!”&lt;/em&gt;&lt;br&gt;
That is useful as a starting point, but it doesn’t solve a real problem.&lt;/p&gt;

&lt;p&gt;In the real world, businesses do not need “Hello World.”&lt;br&gt;
They need &lt;strong&gt;customers booked&lt;/strong&gt;, &lt;strong&gt;schedules organized&lt;/strong&gt;, and &lt;strong&gt;systems that scale without breaking&lt;/strong&gt;.&lt;br&gt;
In this blog, we’ll take a &lt;strong&gt;simple AWS Lambda + API Gateway setup&lt;/strong&gt; and evolve it into a &lt;strong&gt;Smart Appointment Booking &amp;amp; Management API&lt;/strong&gt;. A serverless backend that could realistically power a clinic, salon, or consulting business.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No servers.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;No infrastructure headaches.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Just clean, scalable cloud architecture.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Problem Definition
&lt;/h2&gt;

&lt;p&gt;Most small service-based businesses often face:&lt;br&gt;
• Manual appointment booking&lt;br&gt;
• Double bookings&lt;br&gt;
• No automated reminders&lt;br&gt;
• Poor visibility into schedules&lt;br&gt;
Hiring engineers to manage servers is expensive.&lt;br&gt;
This is where serverless architecture shines.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Serverless Solution Architecture
&lt;/h2&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%2F0z5zk9b7ubxpq0jf28rk.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%2F0z5zk9b7ubxpq0jf28rk.png" alt="Architectural Design" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Active AWS account&lt;/li&gt;
&lt;li&gt;Basic understanding of serverless architecture&lt;/li&gt;
&lt;li&gt;Knowledge of Python or Node.js&lt;/li&gt;
&lt;li&gt;Familiarity with REST APIs and HTTP methods&lt;/li&gt;
&lt;li&gt;Basic understanding of authentication (JWT)&lt;/li&gt;
&lt;li&gt;Fundamental AWS IAM concepts&lt;/li&gt;
&lt;li&gt;Ability to use API testing tools (Postman or cURL)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Now let's dive in!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Creating the Core Lambda Function
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Create the Lambda Function
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Sign into AWS Console &lt;a href="https://signin.aws.amazon.com" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Search for and select &lt;strong&gt;Lambda&lt;/strong&gt; in the global search bar.&lt;/p&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%2Fg5bozjys1ys7pveh8z1w.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%2Fg5bozjys1ys7pveh8z1w.png" alt="Lambda search" width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on the &lt;strong&gt;Create a function&lt;/strong&gt; button. &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%2F6ddsipi6pa2wzyej1tgr.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%2F6ddsipi6pa2wzyej1tgr.png" alt="Create Function" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Select &lt;strong&gt;Author from scratch&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure:&lt;br&gt;
o   Function Name: &lt;em&gt;AppointmentAPI&lt;/em&gt;&lt;br&gt;
o   Runtime: &lt;em&gt;Python 3.x&lt;/em&gt;&lt;br&gt;
o   Execution Role: Create new role with basic permissions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Create Function&lt;/strong&gt; as shown in the images below.&lt;/p&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%2Fz8om5gpj9b43mbe8hfrl.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%2Fz8om5gpj9b43mbe8hfrl.png" alt="lambda function" width="800" height="312"&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%2F9lviwx86rqan51x006eg.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%2F9lviwx86rqan51x006eg.png" alt="lambda function" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Basic Lambda Handler (Foundation)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Go to the &lt;strong&gt;Code&lt;/strong&gt; section of the function you just created and replace the body of code there with the code below.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import json

def lambda_handler(event, context):
    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "Appointment API is running"
        })
    }

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

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Click on the &lt;strong&gt;Deploy&lt;/strong&gt; button to deploy the function.&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%2F4beh7h57mdsk29594581.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%2F4beh7h57mdsk29594581.png" alt="Deploy function" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on the &lt;strong&gt;Test&lt;/strong&gt; button and Create an &lt;strong&gt;Event&lt;/strong&gt; to test your function, then click the &lt;strong&gt;Test&lt;/strong&gt; button as shown below.&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%2Fzpwxm40b8nh15qgcbu1r.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%2Fzpwxm40b8nh15qgcbu1r.png" alt="Create Event" width="799" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The function ran successfully!&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%2Fshkmefq6ei3527r0pjb4.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%2Fshkmefq6ei3527r0pjb4.png" alt="Success" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Exposing the API with API Gateway
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Create REST API
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Search for and select &lt;strong&gt;API Gateway&lt;/strong&gt; in the global search bar.&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%2Fdey2qsuvt7ocp1mepy07.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%2Fdey2qsuvt7ocp1mepy07.png" alt="API Gateway" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Click Create API&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose &lt;strong&gt;REST API&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select &lt;strong&gt;Regional&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select &lt;strong&gt;Security Policy&lt;/strong&gt; &lt;em&gt;(as shown in the image below)&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on &lt;strong&gt;Create API&lt;/strong&gt; button&lt;/p&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%2F1m8wxnot7vtxuv3giymw.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%2F1m8wxnot7vtxuv3giymw.png" alt="API Gateway" width="800" height="376"&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%2Fnp7pk1hkudvy81wsrntv.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%2Fnp7pk1hkudvy81wsrntv.png" alt="API Gateway2" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Create Resource
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Resource name: appointments&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Path: /&lt;/p&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%2Frdr0gp5x9kvcmrkar4ct.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%2Frdr0gp5x9kvcmrkar4ct.png" alt="API Resource" width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Create Methods
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Inside the created API Resource, click on the &lt;strong&gt;Create method&lt;/strong&gt; button.&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%2Fs3fqj1dzmspkno78h7pl.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%2Fs3fqj1dzmspkno78h7pl.png" alt="Create method" width="800" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the following methods:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Method&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Purpose&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;Create appointment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;Fetch appointments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;Update appointment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;Cancel appointment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Each method integrates with the same Lambda function.&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Steps to Create a &lt;strong&gt;Method&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;For a POST /appointments method:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Select the /appointments resource&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click Actions → Create Method&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose POST&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Toggle the switch for &lt;strong&gt;Lambda proxy integration&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set Integration type to &lt;strong&gt;Lambda Function&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable Lambda Proxy Integration &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose your region and select your Lambda (AppointmentAPI)&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Create method&lt;/strong&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%2F0c8b2fa3z25mxzfqwvgh.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%2F0c8b2fa3z25mxzfqwvgh.png" alt="Post method" width="800" height="273"&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%2Fkko8297nm10l1v7g0789.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%2Fkko8297nm10l1v7g0789.png" alt="Post method 2" width="800" height="372"&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%2Fbt7jp4vl63yhijg4z9mh.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%2Fbt7jp4vl63yhijg4z9mh.png" alt="Post method 3" width="800" height="271"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;What it does: Creates a new appointment entry (writes to DynamoDB).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;For a GET /appointments method *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Repeat the same steps, but choose GET:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;/appointments → Actions → Create Method → GET&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Integration type: Lambda Function&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable Lambda Proxy Integration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select the same Lambda function&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Create method&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;What it does: Returns appointments (for a user or provider).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For a PUT /appointments method.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Repeat again for PUT:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;/appointments → Actions → Create Method → PUT&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Integrate with the same Lambda&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable Lambda Proxy Integration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Create method&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;What it does: Updates an appointment (reschedule, status update, etc.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For a DELETE /appointments method.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Repeat for DELETE:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;/appointments → Actions → Create Method → DELETE&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Integrate with same Lambda&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable Lambda Proxy Integration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Create method&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;What it does: Cancels an appointment (soft delete recommended, by setting status = canceled).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The four CRUD Methods.&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%2Fcu0i2svorx1ruggqvq5l.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%2Fcu0i2svorx1ruggqvq5l.png" alt="CRUD Methods" width="800" height="341"&gt;&lt;/a&gt; &lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3: Designing the Data Model (DynamoDB)
&lt;/h2&gt;

&lt;p&gt;Before we write a single line of booking logic, we need to get the data model right. In serverless systems, your database design is everything, because your API performance, cost, and scalability depend on it.&lt;/p&gt;

&lt;p&gt;For this &lt;strong&gt;Smart Appointment Booking API&lt;/strong&gt;, we’ll use &lt;strong&gt;Amazon DynamoDB&lt;/strong&gt;: a fully managed NoSQL database designed for fast reads/writes at massive scale.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting up the DynamoDB Table
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Open DynamoDB&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search for and select &lt;strong&gt;Lambda&lt;/strong&gt; in the global search bar.&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%2Flkzrugiqobckbc1r3ehr.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%2Flkzrugiqobckbc1r3ehr.png" alt=" " width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click DynamoDB → Tables → Create table&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Create the Appointments Table&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
On &lt;strong&gt;Create table&lt;/strong&gt; pane, enter:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Table details&lt;/p&gt;

&lt;p&gt;Table name: Appointments&lt;/p&gt;

&lt;p&gt;Partition key (PK): &lt;code&gt;userId&lt;/code&gt; (Type: String)&lt;/p&gt;

&lt;p&gt;Sort key (SK): &lt;code&gt;appointmentId&lt;/code&gt; (Type: String) &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%2F609v84m8v308hlwqa2w1.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%2F609v84m8v308hlwqa2w1.png" alt="Table details" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Choose Table Settings&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Under Table settings, choose On-demand (Pay per request)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is best for MVPs and unpredictable traffic&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%2F359f6mmq7g9z4ox00cnp.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%2F359f6mmq7g9z4ox00cnp.png" alt=" " width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Create table&lt;/strong&gt; button. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Add a Global Secondary Index (GSI) for Fast Lookups&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;This is optional, but it’s what makes your API feel “real” when you need queries like:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Show all appointments on a date”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Show all booked appointments”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Show all appointments by service type”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Create a GSI for date&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Open the Appointments table&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to Indexes tab&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click Create index&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Index details&lt;/p&gt;

&lt;p&gt;Partition key: &lt;code&gt;date&lt;/code&gt; (String)&lt;/p&gt;

&lt;p&gt;Sort key (optional): &lt;em&gt;time&lt;/em&gt; (String)&lt;/p&gt;

&lt;p&gt;Index name: &lt;code&gt;date-time-index&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Leave other configurations as default&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click Create index&lt;/p&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%2Fxvwhvr7dhwsx21a7map4.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%2Fxvwhvr7dhwsx21a7map4.png" alt="Index" width="800" height="347"&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%2Fti9ekk27vdu8pathncwv.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%2Fti9ekk27vdu8pathncwv.png" alt=" " width="800" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Now you can query: “All appointments on 2026-02-20, sorted by time.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Give Lambda Permission to Use DynamoDB&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Lambda won’t be able to read/write until IAM allows it.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;strong&gt;Lambda&lt;/strong&gt; → our function &lt;strong&gt;(AppointmentAPI)&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open &lt;strong&gt;Configuration&lt;/strong&gt; → &lt;strong&gt;Permissions&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the Execution role (opens IAM)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click Add permissions → Attach policies&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Attach one of these:&lt;/p&gt;

&lt;p&gt;Quick (broad, good for learning) but goes against the &lt;strong&gt;Least Privilege Principle&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;AmazonDynamoDBFullAccess&lt;/p&gt;

&lt;p&gt;Better (recommended for real projects):&lt;/p&gt;

&lt;p&gt;Create a custom policy allowing only:&lt;/p&gt;

&lt;p&gt;dynamodb:PutItem&lt;/p&gt;

&lt;p&gt;dynamodb:GetItem&lt;/p&gt;

&lt;p&gt;dynamodb:UpdateItem&lt;/p&gt;

&lt;p&gt;dynamodb:DeleteItem&lt;/p&gt;

&lt;p&gt;dynamodb:Query&lt;/p&gt;

&lt;p&gt;dynamodb:Scan (optional)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;However, for purpose of this demo, we will use the first option 
(AmazonDynamoDBFullAccess).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Lambda Permission (IAM Role)&lt;/em&gt;&lt;br&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%2Fquy6enofjgufexgwntn5.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%2Fquy6enofjgufexgwntn5.png" alt="Lambda Role" width="800" height="331"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Attach Policy (AmazonDynamoDBFullAccess)&lt;/em&gt;&lt;br&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%2F5i0q3pmjhnbmrlmmkod6.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%2F5i0q3pmjhnbmrlmmkod6.png" alt="IAM Permission" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on &lt;strong&gt;Add permission&lt;/strong&gt; button.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Step 4: Booking an Appointment (Business Logic)
&lt;/h2&gt;

&lt;p&gt;Now that &lt;strong&gt;DynamoDB&lt;/strong&gt; is ready and &lt;strong&gt;API Gateway&lt;/strong&gt; routes requests to &lt;strong&gt;Lambda&lt;/strong&gt;, we are ready to build the real booking engine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here is what we will implement:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;POST /appointments to creates a new booking&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Validates required fields&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generates a unique appointmentId&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Writes the appointment into DynamoDB&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Returns a clean JSON response&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  1. Lambda: Read the Incoming Request
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;When API Gateway triggers Lambda (proxy mode), the body arrives as a string under event["body"].&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What Lambda Receives &lt;/li&gt;
&lt;li&gt;event["httpMethod"] → "POST"&lt;/li&gt;
&lt;li&gt;event["path"] → "/appointments"&lt;/li&gt;
&lt;li&gt;event["body"] → JSON string&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2. Implement Appointment Booking Logic (Python)
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Create appointment + store in DynamoDB&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Since our &lt;strong&gt;DynamoDB table&lt;/strong&gt; is named &lt;strong&gt;Appointments&lt;/strong&gt; and has keys:&lt;br&gt;
&lt;strong&gt;userId&lt;/strong&gt; (PK), &lt;strong&gt;appointmentId&lt;/strong&gt; (SK)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to the Lambda function and add the body of codes below:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import json
import os
import uuid
import boto3
from datetime import datetime

dynamodb = boto3.resource("dynamodb")
TABLE_NAME = os.environ.get("APPOINTMENTS_TABLE", "Appointments")
table = dynamodb.Table(TABLE_NAME)

def lambda_handler(event, context):
    method = event.get("httpMethod")
    path = event.get("path")

    # Route: POST /appointments
    if method == "POST" and path == "/appointments":
        return handle_create_appointment(event)

    return response(404, {"error": "Route not found"})

def handle_create_appointment(event):
    # In a real system, userId comes from Cognito JWT claims.
    # For now, we’ll hardcode a sample user.
    user_id = "user_123"

    # Parse JSON body
    try:
        body = json.loads(event.get("body") or "{}")
    except json.JSONDecodeError:
        return response(400, {"error": "Invalid JSON body"})

    service = body.get("service")
    date = body.get("date")
    time = body.get("time")

    # Validate required fields
    missing = [k for k in ["service", "date", "time"] if not body.get(k)]
    if missing:
        return response(400, {"error": f"Missing fields: {', '.join(missing)}"})

    # Basic input validation (light but helpful)
    if not is_valid_date(date):
        return response(400, {"error": "Invalid date format. Use YYYY-MM-DD"})
    if not is_valid_time(time):
        return response(400, {"error": "Invalid time format. Use HH:MM (24h)"})

    appointment_id = f"apt_{uuid.uuid4().hex[:10]}"
    item = {
        "userId": user_id,
        "appointmentId": appointment_id,
        "service": service,
        "date": date,
        "time": time,
        "status": "BOOKED",
        "createdAt": datetime.utcnow().isoformat() + "Z"
    }

    # Write to DynamoDB
    table.put_item(Item=item)

    # Return response
    return response(201, {
        "appointmentId": appointment_id,
        "status": "BOOKED"
    })

def response(status_code, body):
    return {
        "statusCode": status_code,
        "headers": {
            "Content-Type": "application/json"
        },
        "body": json.dumps(body)
    }

def is_valid_date(date_str):
    try:
        datetime.strptime(date_str, "%Y-%m-%d")
        return True
    except Exception:
        return False

def is_valid_time(time_str):
    try:
        datetime.strptime(time_str, "%H:%M")
        return True
    except Exception:
        return False

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

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Click on Deploy&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  3. Configure Lambda Environment Variable
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Instead of hardcoding table name.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Lambda&lt;/strong&gt; → &lt;strong&gt;our function&lt;/strong&gt; (AppointmentAPI) → &lt;strong&gt;Configuration&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the left pane, click on &lt;strong&gt;Environment variables&lt;/strong&gt; → Edit and &lt;strong&gt;Add environment variable&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Key: APPOINTMENTS_TABLE&lt;/p&gt;

&lt;p&gt;Value: Appointments&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&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%2Ftkpd02vftk9zh93qpxvf.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%2Ftkpd02vftk9zh93qpxvf.png" alt=" " width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Create Stage in API Gateway
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to API Gateway, click on our API (AppointmentRestAPI), on the left pane, under &lt;strong&gt;API: AppointmentRestAPI&lt;/strong&gt;&lt;br&gt;
click on &lt;strong&gt;Stages&lt;/strong&gt;, click on &lt;strong&gt;Create stage&lt;/strong&gt;. Enter the following details:&lt;br&gt;
Stage name: prod&lt;br&gt;
Deployment: select date and time from the draw-down.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Leave others as default and click the &lt;strong&gt;Create stage&lt;/strong&gt; button.&lt;/p&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%2F7g1fxenk3vhmk1pcpcxe.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%2F7g1fxenk3vhmk1pcpcxe.png" alt="Stage" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Then click on deploy using the created stage (prod)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Lambda Triggers API Routes&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to our Lambda function, click on &lt;strong&gt;API Gateway&lt;/strong&gt; under the &lt;strong&gt;AppointmentAPI&lt;/strong&gt; and click on configuration.
To see the triggers (The four routes of POST, GET, PUT and DELETE)&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%2Fi7pfeye7a9tpez6idb5p.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%2Fi7pfeye7a9tpez6idb5p.png" alt=" " width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Test the Endpoint
&lt;/h3&gt;

&lt;p&gt;Using cURL&lt;br&gt;
&lt;em&gt;Replace the URL with your API Gateway Invoke URL:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(In Windows PowerShell)&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Invoke-RestMethod `
  -Method POST `
  -Uri "https://8cjba7n9wb.execute-api.us-east-1.amazonaws.com/prod/appointments" `
  -ContentType "application/json" `
  -Body '{"service":"haircut","date":"2026-02-20","time":"14:00"}'

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response in PowerShell&lt;/strong&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%2Frui34lrlr880zwtshqav.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%2Frui34lrlr880zwtshqav.png" alt="Response" width="790" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table item in DynamoDB table&lt;/strong&gt;&lt;br&gt;
Go to DynamoDB, click on our table and find the new item posted there by the Lambda function, triggered by the API.&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%2Fwbl7ag6o4c12zjvrpo7p.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%2Fwbl7ag6o4c12zjvrpo7p.png" alt=" " width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;At this point, we’ve built a real booking engine.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5: Set Notifications &amp;amp; Reminders (SNS + SES)
&lt;/h2&gt;

&lt;p&gt;Bookings are pointless if people forget them. Real systems reduce no-shows with instant confirmations and scheduled reminders.&lt;/p&gt;

&lt;p&gt;So, let's add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SNS for SMS alerts&lt;/li&gt;
&lt;li&gt;SES for email confirmations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Triggers for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Booking confirmation (immediate)&lt;/li&gt;
&lt;li&gt;24-hour reminder (scheduled)&lt;/li&gt;
&lt;li&gt;Cancellation alert (immediate)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  SMS Notifications with Amazon SNS
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Create (or choose) an SNS setup for SMS&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to Amazon SNS in AWS Console&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the left menu, click Text messaging (SMS) (if shown)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scroll down to SMS preferences (or click Edit SMS preferences if shown) as shown in the images below.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Set:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Default message type → Transactional&lt;/p&gt;

&lt;p&gt;Monthly spending limit → set a small amount (e.g. 1 or 5 USD)&lt;/p&gt;

&lt;p&gt;Click Save changes&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%2Fqxrxyh2imcx8lyiqabn7.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%2Fqxrxyh2imcx8lyiqabn7.png" alt="SNS Setting" width="800" height="284"&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%2Fgg2gs2ga3lakfgb0tnet.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%2Fgg2gs2ga3lakfgb0tnet.png" alt="SMS setting" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In many regions you can send SMS directly without creating a topic (you publish to a phone number).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Test SMS directly from the SNS console (optional but recommended)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On the same screen, click Publish text message (top right)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Enter:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Phone number → your phone number (with country code, e.g. +1...)&lt;/li&gt;
&lt;li&gt;Message → Test SMS from Appointment API&lt;/li&gt;
&lt;li&gt;Click Publish message&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%2Fa9qmgvfnfkk5vlh5y04v.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%2Fa9qmgvfnfkk5vlh5y04v.png" alt="Publish sms" width="800" height="350"&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%2F07vekz2lifmu7mmohnur.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%2F07vekz2lifmu7mmohnur.png" alt="varify" width="800" height="247"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;If you receive the SMS, SNS is working correctly.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Allow your Lambda to send SMS&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to AWS Lambda&lt;/li&gt;
&lt;li&gt;Open your function (e.g. AppointmentAPI)&lt;/li&gt;
&lt;li&gt;Go to Configuration → Permissions&lt;/li&gt;
&lt;li&gt;Click the Execution role (opens IAM)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Attach this policy:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AmazonSNSFullAccess &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%2Fmdc3yniokx3b7pl7mhus.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%2Fmdc3yniokx3b7pl7mhus.png" alt=" " width="800" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scroll down and click &lt;strong&gt;Add permissions&lt;/strong&gt; button.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Lambda can now send SMS.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Send SMS from Lambda (this is all you need)&lt;/strong&gt;&lt;br&gt;
Add the body of code below to our Lambda code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import boto3

sns = boto3.client("sns")

def send_sms(phone_number, message):
    sns.publish(
        PhoneNumber=phone_number,
        Message=message
    )

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response (Booking confirmation)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;send_sms(
    "+15551234567",
    "Booking confirmed: Haircut on 2026-02-20 at 14:00"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. When SMS is triggered&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Booking confirmation&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;After successful POST /appointments&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
Cancellation alert&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;After DELETE /appointments or status update to canceled&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
24-hour reminder&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Sent from a scheduled Lambda (step no includede here)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Epilogue: Completing the Journey to a Full SaaS Backend
&lt;/h2&gt;

&lt;p&gt;What we’ve built throughout this blog is a solid foundation, a functional, real-world serverless backend capable of handling bookings, persistence, and user-facing workflows. But modern SaaS systems don’t stop at functionality alone. They mature through security, reliability, deployment discipline, and cost awareness.&lt;/p&gt;

&lt;p&gt;To evolve this solution into a fully production-grade SaaS backend, the following layers naturally complement what we have already covered:&lt;/p&gt;

&lt;h3&gt;
  
  
  Securing the API with Authentication
&lt;/h3&gt;

&lt;p&gt;Adding authentication ensures that every request is trusted and every action is tied to an authenticated identity. This is where user isolation, role-based access, and data protection come into play, which is critical for any real application handling customer data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring &amp;amp; Reliability
&lt;/h3&gt;

&lt;p&gt;Visibility is what turns a working system into a reliable one. By introducing monitoring, logs, and alerts, the system becomes observable, debuggable, and resilient under real traffic and failure scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment Strategy
&lt;/h3&gt;

&lt;p&gt;A proper deployment strategy, separating development, staging, and production environments, that allows changes to be released safely and confidently. This transforms the project from a single deployment into a system that can evolve continuously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost Breakdown: Why Serverless Wins
&lt;/h3&gt;

&lt;p&gt;Understanding the cost model completes the architectural picture. With serverless, you pay only for usage, scale automatically with demand, and eliminate idle infrastructure—making this approach especially powerful for startups and growing businesses.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Final Result
&lt;/h3&gt;

&lt;p&gt;When these layers are added on top of the system we have built, the result is a complete SaaS-ready backend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secure&lt;/li&gt;
&lt;li&gt;Scalable&lt;/li&gt;
&lt;li&gt;Production-ready&lt;/li&gt;
&lt;li&gt;Business-focused&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This architecture is not theoretical. It is directly applicable to real industries and real customers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This system could power:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clinics managing patient appointments&lt;/li&gt;
&lt;li&gt;Salons handling bookings and reminders&lt;/li&gt;
&lt;li&gt;Consultants scheduling client sessions&lt;/li&gt;
&lt;li&gt;Repair services coordinating visits and follow-ups&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing Thought
&lt;/h2&gt;

&lt;p&gt;The true value of serverless architecture isn’t just removing servers, it is in enabling teams to focus on business logic, user experience, and growth instead of infrastructure. What we have built here is not an endpoint, but a platform, one that can be extended, secured, observed, and scaled into a real product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;That is the difference between a tutorial and a real SaaS system.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>programming</category>
      <category>serverless</category>
    </item>
    <item>
      <title>AWS Lightsail in Action: Launching and Scaling Your Web Server Efficiently.</title>
      <dc:creator>Bernard Chika Uwaezuoke</dc:creator>
      <pubDate>Sun, 20 Apr 2025 17:03:23 +0000</pubDate>
      <link>https://dev.to/donhadley22/aws-lightsail-in-action-launching-and-scaling-your-web-server-efficiently-2pn6</link>
      <guid>https://dev.to/donhadley22/aws-lightsail-in-action-launching-and-scaling-your-web-server-efficiently-2pn6</guid>
      <description>&lt;h1&gt;
  
  
  Description
&lt;/h1&gt;

&lt;p&gt;Deploy a Java web app to AWS Lightsail using Jenkins for CI/CD. Learn how to launch, automate, and scale your app with this hands-on tutorial.&lt;/p&gt;

&lt;p&gt;Are you looking for a fast, affordable, and beginner-friendly way to deploy your Java app to the cloud?&lt;/p&gt;

&lt;h2&gt;
  
  
  In this guide, you'll learn how to:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Launch an AWS Lightsail instance&lt;/li&gt;
&lt;li&gt;Use Jenkins to build and deploy a containerized Java web application&lt;/li&gt;
&lt;li&gt;Scale your application smartly&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;h2&gt;
  
  
  AWS Lightsail
&lt;/h2&gt;

&lt;p&gt;AWS Lightsail is a lightweight Virtual Private Server (VPS) service by Amazon Web Services, perfect for developers and small teams looking to deploy applications without diving into complex AWS configurations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features of AWS Lightsail
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Straightforward Pricing with Predictable Costs
&lt;/h4&gt;

&lt;p&gt;One of Lightsail’s biggest advantages is its flat-rate pricing model. Unlike EC2’s pay-as-you-go model, Lightsail bundles compute resources, SSD storage, and data transfer into fixed monthly plans, starting as low as $5/month. This makes budgeting easier, especially for developers working on side projects or small teams managing costs without sacrificing performance.&lt;/p&gt;

&lt;h4&gt;
  
  
  Quick Instance Setup and Pre-configured Applications
&lt;/h4&gt;

&lt;p&gt;Starting with AWS Lightsail is incredibly easy. Users can launch a virtual machine (instance) in just a few clicks. You can choose your preferred operating system (Linux or Windows) or select from a range of pre-configured application stacks such as WordPress, Node.js, LAMP, and more. This allows developers to skip lengthy server setup processes and get their applications online faster.&lt;/p&gt;

&lt;h4&gt;
  
  
  Built-in Networking and Management Tools
&lt;/h4&gt;

&lt;p&gt;Lightsail includes several built-in tools to simplify server management. Users can easily assign static IPs, configure DNS settings, manage firewalls, and even integrate load balancers for high availability setups. These features are accessible via a user-friendly web interface, which removes the need for complex networking configurations typically associated with EC2 or other AWS infrastructure.&lt;/p&gt;

&lt;h4&gt;
  
  
  Snapshots, Backups, and High Availability
&lt;/h4&gt;

&lt;p&gt;Data protection and recovery are made simple through Lightsail’s snapshot feature. You can create snapshots of your instances and storage volumes either manually or automatically. These snapshots can be used to restore data quickly or clone environments for testing or scaling. Lightsail also supports high availability by allowing load balancer integration and deploying across different availability zones.&lt;/p&gt;

&lt;h4&gt;
  
  
  Seamless Migration to EC2
&lt;/h4&gt;

&lt;p&gt;When your project outgrows Lightsail’s capabilities, migrating to EC2 is straightforward. Lightsail offers a built-in export tool that converts your instance into an EC2-compatible AMI. This makes it easy to transition to a more flexible and powerful AWS environment while maintaining your application setup and configurations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ideal Use Cases
&lt;/h3&gt;

&lt;p&gt;AWS Lightsail is best suited for lightweight workloads and projects that require quick deployment and minimal maintenance. It is ideal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Hosting blogs, portfolios, and e-commerce websites&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Running small-scale web applications and APIs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating development or staging environments&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deploying MVPs (Minimum Viable Products)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Students or beginners learning server deployment&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Please ensure you have the following before we begin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AWS account&lt;/li&gt;
&lt;li&gt;Jenkins installed (locally or remotely)&lt;/li&gt;
&lt;li&gt;Apache Maven installed (locally or remotely)&lt;/li&gt;
&lt;li&gt;A Java project (using Maven or Gradle)&lt;/li&gt;
&lt;li&gt;Docker installed (locally or remotely)&lt;/li&gt;
&lt;li&gt;Basic familiarity with terminal commands&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Step 1: Create a Lightsail Instance
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
Visit &lt;a href="https://lightsail.aws.amazon.com/" rel="noopener noreferrer"&gt;AWS Lightsail&lt;/a&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%2Fqy2utbvnsrlu7fb6in15.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%2Fqy2utbvnsrlu7fb6in15.png" alt="AWS Lightsail landing page" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Create instance&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select your instance location. For this project, I chose &lt;strong&gt;Virginia&lt;/strong&gt; &lt;code&gt;us-east-1&lt;/code&gt; region.&lt;/p&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%2Fb3964ka7bhz7751m7y29.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%2Fb3964ka7bhz7751m7y29.png" alt="Select Region" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Choose your instance image; this is the operating system.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Platform:&lt;/strong&gt; Linux/Unix&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blueprint:&lt;/strong&gt; OS-only (Ubuntu)
For this demo, I chose Ubuntu distribution of Linux operating system without any &lt;strong&gt;Apps&lt;/strong&gt; added.&lt;/li&gt;
&lt;/ul&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%2Fn3lffqwebesb1mt7yonc.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%2Fn3lffqwebesb1mt7yonc.png" alt="Choose OS" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Pick an instance plan from (as low as $5/month). This is one of the major benefits of the &lt;strong&gt;AWS Lightsail&lt;/strong&gt; it removes the unpredictability of cloud costs and allows small businesses to operate within a tight budget.&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%2Fth7bmmtjjagoain86ik2.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%2Fth7bmmtjjagoain86ik2.png" alt="Pick instance plan" width="652" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Give your instance a unique name; this will enable you to identify it, and then click on the &lt;strong&gt;Create Instance&lt;/strong&gt; button to launch it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The instance starting&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on the Instance icon to see the details&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%2Fwksclgy81eyom0nanh9k.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%2Fwksclgy81eyom0nanh9k.png" alt="Instance Lunched" width="748" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Download the &lt;code&gt;.pem&lt;/code&gt; private SSH key. You need this to connect to the instance from an SSH agent or terminal. For this project, we connected directly from the Lightsail management console.&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%2Ff5eo6iw0ckciz74vo7x8.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%2Ff5eo6iw0ckciz74vo7x8.png" alt="Download Private Key" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on the &lt;strong&gt;Networking&lt;/strong&gt; tab and scroll down to &lt;strong&gt;IPv4 Firewall&lt;/strong&gt; under it click on &lt;strong&gt;Add rule&lt;/strong&gt;. This will open a pane to configure inbound rule. Under &lt;strong&gt;Application&lt;/strong&gt;, select &lt;strong&gt;Custom&lt;/strong&gt;, for &lt;strong&gt;Protocol&lt;/strong&gt; select &lt;strong&gt;TCP&lt;/strong&gt; and &lt;strong&gt;Port or range&lt;/strong&gt; enter &lt;strong&gt;8080&lt;/strong&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%2F4cg1alhkkvcevghmn5bf.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%2F4cg1alhkkvcevghmn5bf.png" alt="Inbound Port" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 2: Connect to Your Instance
&lt;/h1&gt;

&lt;p&gt;Use the command below if you are connecting from a command line.&lt;br&gt;
&lt;code&gt;ssh -i ~/Downloads/lightsail.pem ubuntu@your-lightsail-ip&lt;/code&gt;&lt;br&gt;
For this project, I will be connecting from the Lightsail console.&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%2Fuv2oeubdavsqcejjbxmi.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%2Fuv2oeubdavsqcejjbxmi.png" alt="Connect to Lightsail Instance" width="602" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Ubuntu Lightsail Instance Terminal&lt;/em&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%2Fhhrtxrjdlxmct2n0hpo0.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%2Fhhrtxrjdlxmct2n0hpo0.png" alt="Lightsail Instance Terminal" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once connected, run the following commands to update the server and install &lt;strong&gt;Java&lt;/strong&gt; and &lt;strong&gt;Docker&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt upgrade -y

sudo apt install -y default-jdk

java -version  

sudo apt install -y apt-transport-https ca-certificates curl software-properties-common gnupg lsb-release

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null

sudo apt update

sudo apt install -y docker-ce docker-ce-cli containerd.io

docker --version
sudo systemctl status docker

sudo usermod -aG docker $USER
newgrp docker  

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Checking Java Version&lt;/em&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%2F1dxcq2q6499p2jygghrc.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%2F1dxcq2q6499p2jygghrc.png" alt="Java Version" width="800" height="135"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 3: Deploy the Application
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clone the Project Repository:&lt;/strong&gt;
Clone the sample Java application repository from GitHub. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;git clone https://github.com/Donhadley22/practice-java-app.git&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This repository contains a basic Java project, Dockerfile and Jenkinsfile. These will be used to build a Docker image, test, and deploy the application to AWS Lightsail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here is the Jenkinsfile:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pipeline {
  agent any
     tools {
    maven 'maven-3.9.9'
  }

  environment {
    DOCKER_IMAGE          = 'donhadley/peabsmartapp'
    DOCKERFILE            = 'Dockerfile'
    DOCKER_REGISTRY       = 'index.docker.io/v1'
    DOCKER_CREDENTIALS_ID = 'dockerhub-creds'
    DOCKER_TAG            = "${env.BUILD_NUMBER}"
    LS_INSTANCE_ID       = 'i-05d37916155efbc80'
    LS_REGION            = 'us-east-1'
    SSH_CREDENTIALS_ID    = 'ec2-ssh-key'
    LS_USER              = 'ubuntu'
    LS_HOST              = '52.87.109.141'
    CONTAINER_NAME        = 'peabsmart-app'
    HOST_PORT             = 80
    CONTAINER_PORT        = 8080
  }

  stages {

    stage('Checkout') {
      steps {
        echo 'Checking out code...'
        checkout scm
      }
    }

    stage('Package') {
      steps {
        echo 'Building the Java project and packaging WAR...'
          sh 'mvn clean package'
  }
}

stage('Test') {
      steps {
        echo 'Running tests...'
         sh 'mvn test'
      }
    }
    stage('Build') {
      steps {
        echo 'Building Docker image...'
        script {
          docker.build("${DOCKER_IMAGE}:${DOCKER_TAG}", "-f ${DOCKERFILE} .")
        }
      }
    }

    stage('Push') {
      steps {
        echo 'Pushing Docker image to Docker Hub...'
        script {
          docker.withRegistry('https://index.docker.io/v1/', "${DOCKER_CREDENTIALS_ID}") {
          docker.image("${DOCKER_IMAGE}:${DOCKER_TAG}").push()
      }
    }
  }
}


    stage('Deploy') {
      steps {
        echo 'Deploying to AWS EC2...'
        script {
          sshagent([SSH_CREDENTIALS_ID]) {
            sh """
              ssh -o StrictHostKeyChecking=no ${EC2_USER}@${EC2_HOST} \\
              'docker pull ${DOCKER_IMAGE}:${DOCKER_TAG} &amp;amp;&amp;amp; \
              docker stop ${CONTAINER_NAME} || true &amp;amp;&amp;amp; \
              docker rm ${CONTAINER_NAME} || true &amp;amp;&amp;amp; \
              docker run -d --name ${CONTAINER_NAME} -p ${HOST_PORT}:${CONTAINER_PORT} ${DOCKER_IMAGE}:${DOCKER_TAG}'
             """

          }
        }
      }
    }
  }

  post {
    success {
      echo "✅ Deployed successfully to EC2 🎉"
    }
    failure {
      echo "❌ Deployment failed. Please check the logs for details."
    }
  }
}
// This Jenkinsfile is a declarative pipeline that automates the process of building, testing, and deploying a Dockerized Java application to an AWS instance. It includes stages for checking out the code, building the Docker image, running tests, pushing the image to Docker Hub, and deploying it to the Lightsail instance. The pipeline uses environment variables for configuration and SSH credentials for secure access to the instance.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Make sure the Jenkins server has:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Access to your .pem key&lt;/p&gt;

&lt;p&gt;Maven installed&lt;/p&gt;

&lt;p&gt;Correct permissions on the Lightsail instance&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Setting-up the Jenkins Pipeline job:&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Login to Jenkins with your credentials, and on the &lt;strong&gt;Dashboard&lt;/strong&gt; click on &lt;strong&gt;New item&lt;/strong&gt;.&lt;/p&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%2Fi0g6rlkn3gal4cao2ggp.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%2Fi0g6rlkn3gal4cao2ggp.png" alt="New Item" width="347" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On your &lt;strong&gt;New item&lt;/strong&gt; pane, enter the pipeline job name, select &lt;strong&gt;Multibranch Pipeline&lt;/strong&gt; and click &lt;strong&gt;OK&lt;/strong&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%2F163l2sslg92yy1ubwcss.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%2F163l2sslg92yy1ubwcss.png" alt="Pipeline name" width="682" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On the pipeline &lt;strong&gt;Configuration&lt;/strong&gt; pane under &lt;strong&gt;General&lt;/strong&gt;, enter the &lt;strong&gt;Display Name&lt;/strong&gt; and &lt;strong&gt;Description&lt;/strong&gt; of your pipeline job.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scroll down to the &lt;strong&gt;Branch Sources&lt;/strong&gt; and select &lt;strong&gt;GitHub&lt;/strong&gt;, enter your GitHub credentials (Username and access token), and select it. Also paste the &lt;strong&gt;GitHub HTTP URL&lt;/strong&gt; under &lt;strong&gt;Repository HTTPS URL&lt;/strong&gt; for this project.&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%2Fwe9pox59kj8d2nospg67.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%2Fwe9pox59kj8d2nospg67.png" alt="General Configuration" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on the &lt;strong&gt;Validate&lt;/strong&gt; button to ensure the credential is okay and Jenkins can access the repository in GitHub.&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%2F1nthixwoeenh06fqgman.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%2F1nthixwoeenh06fqgman.png" alt="Validate credential" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In &lt;strong&gt;Build Configuration&lt;/strong&gt;, under &lt;strong&gt;Mode&lt;/strong&gt;, enter by Jenkinsfile and Script Path Jenkinsfile. In most cases Jenkins does these automatically.&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%2F42itrbco04w71aif7ao9.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%2F42itrbco04w71aif7ao9.png" alt="Jenkinsfile config" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click Save and Jenkins will scan the Repository to find the Jenkinsfile.&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%2Fasxbggudwniaxgraew55.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%2Fasxbggudwniaxgraew55.png" alt="Scan Repo for Jenkinsfile" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to the Jenkins &lt;strong&gt;Dashboard&lt;/strong&gt;, Click on the pipeline you just created and select &lt;strong&gt;Build now&lt;/strong&gt;. This will trigger the job to run the various stages declared in the Jenkinsfile.&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%2Fdcrjyfrziggxndwyu8g3.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%2Fdcrjyfrziggxndwyu8g3.png" alt="Job completed" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 4: Access Your Java App
&lt;/h1&gt;

&lt;p&gt;Once deployed, visit:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://&amp;lt;your-lightsail-ip&amp;gt;:8080&lt;/code&gt;&lt;br&gt;
Your Java app should now be live! &lt;br&gt;
Make use of the Public IP Address of your Lightsail instance.&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%2Fc7updqbhdysv9uexli8s.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%2Fc7updqbhdysv9uexli8s.png" alt="The web application" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Scale Smartly with AWS Lightsail
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Scale with Confidence!&lt;/strong&gt;&lt;/em&gt;&lt;br&gt;
These are Lightsail features that grow with you and your business,&lt;br&gt;
&lt;strong&gt;Add Snapshots&lt;/strong&gt; for &lt;strong&gt;Backup&lt;/strong&gt; and &lt;strong&gt;Disaster Recovery&lt;/strong&gt;.&lt;br&gt;
Snapshots in Amazon Lightsail allow you to create point-in-time backups of your instances, databases, and block storage disks. These snapshots can be used to restore your resources in case of data loss or to create new resources based on the snapshot.​&lt;/p&gt;

&lt;h2&gt;
  
  
  How to create a snapshot
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
Navigate to the &lt;strong&gt;Lightsail&lt;/strong&gt; console.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Select your** Instance**.&lt;/p&gt;

&lt;p&gt;Go to the &lt;strong&gt;Snapshots&lt;/strong&gt; tab.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Create snapshot&lt;/strong&gt;, provide a name, and &lt;strong&gt;confirm&lt;/strong&gt;.​&lt;/p&gt;

&lt;p&gt;You can also enable automatic snapshots to have Lightsail create daily backups during a specified time window. ​&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a Load Balancer for High Availability
&lt;/h2&gt;

&lt;p&gt;Lightsail Load Balancers distribute incoming traffic across multiple instances, enhancing the availability and fault tolerance of your application. They automatically perform health checks and route traffic only to healthy instances.​&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps to set up a load balancer
&lt;/h3&gt;

&lt;p&gt;In the Lightsail console, go to the &lt;strong&gt;Networking&lt;/strong&gt; tab.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Create Load Balancer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Assign a name and select the region.&lt;/p&gt;

&lt;p&gt;Attach your instances to the load balancer.​&lt;/p&gt;

&lt;p&gt;This setup ensures that your application remains accessible even if one or more instances fail. ​This is only applicable when you have at least two or more Lightsail instances running same application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attach Additional Storage Blocks
&lt;/h2&gt;

&lt;p&gt;As your data grows, you might need more storage. Lightsail Block Storage allows you to attach additional SSD-backed storage disks to your instances.​&lt;/p&gt;

&lt;h3&gt;
  
  
  To add and attach a storage disk
&lt;/h3&gt;

&lt;p&gt;In the Lightsail console, select your &lt;strong&gt;instance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Go to the &lt;strong&gt;Storage&lt;/strong&gt; tab and click &lt;strong&gt;Create disk&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Specify the disk size and name.&lt;/p&gt;

&lt;p&gt;After creation, attach the disk to your instance.&lt;/p&gt;

&lt;p&gt;Connect to your instance via SSH to format and mount the new disk. ​&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Lightsail Containers for Docker Apps
&lt;/h2&gt;

&lt;p&gt;Lightsail Container Services enable you to deploy and manage containerized applications without managing the underlying infrastructure. You can push your Docker images directly or use images from public registries.​&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying a containerized application
&lt;/h3&gt;

&lt;p&gt;In the Lightsail console, navigate to &lt;strong&gt;Containers&lt;/strong&gt; on the left pane.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Create container service&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Define the service &lt;strong&gt;name&lt;/strong&gt;, &lt;strong&gt;power&lt;/strong&gt;, and &lt;strong&gt;scale&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Add your container by specifying the image and deployment settings.&lt;/p&gt;

&lt;p&gt;Deploy the service and access it via the provided endpoint. ​&lt;/p&gt;

&lt;h2&gt;
  
  
  Seamless Migration to EC2 or ECS
&lt;/h2&gt;

&lt;p&gt;When your application outgrows Lightsail's capabilities, you can migrate to Amazon EC2 or Amazon ECS for more advanced features.​&lt;/p&gt;

&lt;h3&gt;
  
  
  Migrating to EC2:
&lt;/h3&gt;

&lt;p&gt;Create a snapshot of your Lightsail instance.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;Snapshots&lt;/strong&gt; tab, select the snapshot and click &lt;strong&gt;Export&lt;/strong&gt; to &lt;strong&gt;Amazon EC2&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Follow the prompts to create an Amazon Machine Image (AMI) in EC2.&lt;/p&gt;

&lt;p&gt;Launch a new EC2 instance using the exported AMI. ​&lt;/p&gt;

&lt;p&gt;This process allows you to retain your application's configuration and data while transitioning to a more scalable environment.​&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;In this guide, you learned how to deploy and manage a Java application using Amazon Lightsail, integrating essential DevOps practices for scalability and reliability.​&lt;/p&gt;

&lt;p&gt;You began by launching a Lightsail instance, selecting the appropriate operating system and instance plan tailored to your application's needs. This process provided you with a virtual private server equipped with a static IP, enabling consistent access and management.​&lt;/p&gt;

&lt;p&gt;Next, you configured a Jenkins pipeline job on your existing Jenkins server, integrating it with your GitHub repository containing the application code and Jenkinsfile. This setup enabled automated builds and deployments, ensuring that code changes are efficiently tested and deployed.​&lt;/p&gt;

&lt;p&gt;You then deployed your Java application as a Docker image to the server. By containerizing your application, you ensured consistency across development and production environments, simplified dependency management, and facilitated scalability.​&lt;br&gt;
Jenkins&lt;/p&gt;

&lt;p&gt;To prepare for future growth and ensure high availability, you explored scaling strategies. This included adding snapshots for backup and disaster recovery, using load balancers to distribute traffic across multiple instances, attaching additional storage blocks to accommodate growing data needs, and leveraging Lightsail Containers for deploying Dockerized applications. These features empower you to scale your application seamlessly and maintain uptime during peak usage.​&lt;/p&gt;

&lt;p&gt;Finally, you learned about the seamless migration path from Lightsail to more advanced AWS services like EC2 or ECS. This transition allows you to leverage a broader set of tools and configurations as your application's requirements evolve.​&lt;/p&gt;

&lt;p&gt;By following this guide, you've established a robust foundation for deploying, managing, and scaling your Java application in the cloud, ensuring resilience and adaptability in a dynamic digital landscape.&lt;/p&gt;

&lt;h1&gt;
  
  
  Ready to Elevate Your DevOps Journey?
&lt;/h1&gt;

&lt;p&gt;I hope this guide has provided you with valuable insights into deploying and managing your Java application using Amazon Lightsail. If you have any questions, need further clarification, or have suggestions for future topics, feel free to reach out.​&lt;/p&gt;

&lt;p&gt;Stay connected by following my blog for more tutorials and updates. Your feedback and engagement are always welcome!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>java</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Configure a project and repository structure to support secure pipelines in Azure DevOps</title>
      <dc:creator>Bernard Chika Uwaezuoke</dc:creator>
      <pubDate>Mon, 31 Mar 2025 18:00:19 +0000</pubDate>
      <link>https://dev.to/donhadley22/configure-a-project-and-repository-structure-to-support-secure-pipelines-in-azure-devops-43lc</link>
      <guid>https://dev.to/donhadley22/configure-a-project-and-repository-structure-to-support-secure-pipelines-in-azure-devops-43lc</guid>
      <description>&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%2Fy2pq61ufiflobxqtwo2r.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%2Fy2pq61ufiflobxqtwo2r.png" alt="Image description" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>devops</category>
      <category>security</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Let’s Hack the Cloud: Cost-Reduction Strategies Across the Cloud Space. (AWS Lambda as a case study)</title>
      <dc:creator>Bernard Chika Uwaezuoke</dc:creator>
      <pubDate>Sun, 26 Jan 2025 14:30:47 +0000</pubDate>
      <link>https://dev.to/donhadley22/lets-hack-the-cloud-cost-reduction-strategies-across-the-cloud-space-aws-lambda-as-a-case-study-1l75</link>
      <guid>https://dev.to/donhadley22/lets-hack-the-cloud-cost-reduction-strategies-across-the-cloud-space-aws-lambda-as-a-case-study-1l75</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Cloud computing has revolutionized how businesses manage and scale their operations. However, with great flexibility comes the challenge of managing costs effectively. For companies navigating the cloud space, cost optimization is not just an operational necessity but a competitive advantage.&lt;br&gt;
In this blog, we’ll discuss some proven strategies for reducing cloud costs and dive deep into the transformative role of Lambda Functions in cutting infrastructure expenses.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Importance of Cloud Cost Optimization&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The cloud offers scalable, pay-as-you-go pricing, but companies often face ballooning costs due to:&lt;br&gt;
• Over-provisioned resources.&lt;br&gt;
• Idle or underutilized infrastructure.&lt;br&gt;
• Lack of visibility into resource consumption.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost optimization in the cloud is critical for:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Improving ROI on cloud investments.&lt;/li&gt;
&lt;li&gt; Ensuring scalability without excessive expenses.&lt;/li&gt;
&lt;li&gt; Allowing startups and enterprises alike to focus budgets on innovation rather than overhead.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Role of Serverless Computing in Cost Optimization&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is Serverless Computing?&lt;/strong&gt;&lt;br&gt;
Serverless computing allows developers to build and deploy applications without managing underlying infrastructure. In serverless, the cloud provider automatically provisions and scales resources as needed, and users are billed only for the execution time of their code.&lt;br&gt;
One of the most popular serverless offerings is &lt;strong&gt;AWS Lambda&lt;/strong&gt;, but other cloud providers offer equivalents:&lt;br&gt;
• Azure Functions (Microsoft Azure)&lt;br&gt;
• Google Cloud Functions (Google Cloud Platform)&lt;br&gt;
• Cloud Run (Google Cloud for containerized workloads)&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Lambda Functions: A Game-Changer for Cost Reduction&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What Are Lambda Functions?&lt;/strong&gt;&lt;br&gt;
Lambda functions are small units of compute that execute code in response to triggers or events. They run only when needed, making them highly efficient for a wide range of use cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How Lambda Functions Reduce Cloud Costs&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1.    Pay-Per-Use Pricing Model:&lt;/strong&gt;&lt;br&gt;
Unlike traditional infrastructure where you pay for allocated resources regardless of usage, Lambda charges only for the actual time your code runs (measured in milliseconds). For example, running a virtual machine 24/7 costs significantly more than triggering a Lambda function 1,000 times per day for a few milliseconds each time.&lt;br&gt;
&lt;strong&gt;2.    Elimination of Idle Resources:&lt;/strong&gt;&lt;br&gt;
In traditional setups, servers are often idle during off-peak hours, wasting money. Lambda scales automatically with workload demands, ensuring you never pay for unused capacity.&lt;br&gt;
&lt;strong&gt;3.    Simplified Infrastructure:&lt;/strong&gt;&lt;br&gt;
Lambda reduces the need for complex infrastructure. By abstracting server management, it saves costs associated with maintaining and monitoring VMs or container clusters.&lt;br&gt;
&lt;strong&gt;4.    Integration with Other Cloud Services:&lt;/strong&gt;&lt;br&gt;
Lambda seamlessly integrates with cloud services like S3, DynamoDB, and API Gateway, creating cost-efficient workflows without additional orchestration tools.&lt;br&gt;
&lt;strong&gt;5.    Reduced Maintenance Costs:&lt;/strong&gt;&lt;br&gt;
No patching, updates, or hardware replacement. Lambda eliminates the operational overhead and associated costs of maintaining traditional servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Use Cases Where Lambda Excels in Cost Reduction&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1.    Data Processing:&lt;/strong&gt;&lt;br&gt;
Transforming or analyzing logs stored in S3 buckets.&lt;br&gt;
&lt;strong&gt;Cost-Saving Aspect:&lt;/strong&gt; Instead of provisioning an EC2 instance to continuously run processing jobs, Lambda triggers only when new data arrives.&lt;br&gt;
&lt;strong&gt;2.    Event-Driven Applications:&lt;/strong&gt;&lt;br&gt;
 Auto-scaling customer notification services.&lt;br&gt;
&lt;strong&gt;Cost-Saving Aspect:&lt;/strong&gt; Scale dynamically based on the number of notifications, reducing costs during low activity periods.&lt;br&gt;
&lt;strong&gt;3.    Microservices:&lt;/strong&gt;&lt;br&gt;
Building microservices architecture with APIs.&lt;br&gt;
&lt;strong&gt;Cost-Saving Aspect:&lt;/strong&gt; Each service runs independently, scaling and billing separately, preventing over-provisioning.&lt;br&gt;
&lt;strong&gt;4.    Scheduled Tasks:&lt;/strong&gt;&lt;br&gt;
Running periodic jobs like backups or system checks.&lt;br&gt;
&lt;strong&gt;Cost-Saving Aspect:&lt;/strong&gt; Replace always-on cron jobs with Lambda functions triggered at specified intervals.&lt;br&gt;
&lt;strong&gt;5.    Application Prototypes:&lt;/strong&gt;&lt;br&gt;
 Building and testing proof-of-concept applications.&lt;br&gt;
&lt;strong&gt;Cost-Saving Aspect:&lt;/strong&gt; Start small with minimal costs, scaling up only when the application reaches production.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Best Practices to Maximize Cost Savings with Lambda&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1.    Right-Sizing Memory Allocation:&lt;/strong&gt;&lt;br&gt;
Choose the appropriate memory size for your Lambda functions to avoid overpaying for unnecessary capacity.&lt;br&gt;
&lt;strong&gt;2.    Optimize Function Duration:&lt;/strong&gt;&lt;br&gt;
Streamline your code to reduce execution time, minimizing the cost per invocation.&lt;br&gt;
&lt;strong&gt;3.    Use Reserved Concurrency:&lt;/strong&gt;&lt;br&gt;
For predictable workloads, reserve concurrency limits to control costs and ensure availability.&lt;br&gt;
&lt;strong&gt;4.    Monitor Usage with AWS Cost Explorer:&lt;/strong&gt;&lt;br&gt;
Continuously track Lambda usage patterns and costs to identify optimization opportunities.&lt;br&gt;
&lt;strong&gt;5.    Leverage AWS Free Tier:&lt;/strong&gt;&lt;br&gt;
Take advantage of Lambda’s free tier, which provides 1 million requests and 400,000 GB-seconds of compute time per month.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Beyond Lambda: General Cloud Cost Optimization Tips&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1.    Auto-Scaling:&lt;/strong&gt;&lt;br&gt;
Enable auto-scaling for resources like EC2 instances and Kubernetes clusters to handle varying workloads efficiently.&lt;br&gt;
&lt;strong&gt;2.    Use Spot and Reserved Instances:&lt;/strong&gt;&lt;br&gt;
Leverage spot instances for non-critical workloads and reserved instances for predictable workloads.&lt;br&gt;
&lt;strong&gt;3.    Monitor and Optimize Storage:&lt;/strong&gt;&lt;br&gt;
Regularly audit and remove unused snapshots and use lifecycle policies for S3 storage.&lt;br&gt;
&lt;strong&gt;4.    Rightsize Resources:&lt;/strong&gt;&lt;br&gt;
Continuously evaluate and resize over-provisioned resources using tools like Azure Advisor or AWS Compute Optimizer.&lt;br&gt;
&lt;strong&gt;5.    Implement Budgets and Alerts:&lt;/strong&gt;&lt;br&gt;
Set cost budgets and configure alerts to prevent unexpected overspending.&lt;/p&gt;

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

&lt;p&gt;Reducing cloud costs doesn’t mean sacrificing performance or scalability, it’s about leveraging the right tools and strategies. Lambda functions, with their pay-per-use model and automatic scaling, exemplify how serverless computing can dramatically cut infrastructure expenses.&lt;br&gt;
By adopting a thoughtful approach to cloud resource management, companies can strike the perfect balance between cost and efficiency, ensuring sustainable growth in the cloud era.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Let’s hack the cloud together!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>cloudcomputing</category>
      <category>lambda</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Configuring Agent Pools and Understanding Pipeline Styles in Azure DevOps</title>
      <dc:creator>Bernard Chika Uwaezuoke</dc:creator>
      <pubDate>Mon, 05 Feb 2024 09:48:54 +0000</pubDate>
      <link>https://dev.to/donhadley22/configuring-agent-pools-and-understanding-pipeline-styles-in-azure-devops-540p</link>
      <guid>https://dev.to/donhadley22/configuring-agent-pools-and-understanding-pipeline-styles-in-azure-devops-540p</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;strong&gt;This blog is inspired by a Microsoft Lab with similar tittle&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;INTRODUCTION&lt;/strong&gt;&lt;br&gt;
Welcome to our latest exploration into the dynamic realm of Azure DevOps! In today's blog post, we delve into the crucial aspects of configuring agent pools and unravel the intricacies of pipeline styles. Navigating the ever-evolving landscape of DevOps requires a deep understanding of these fundamental components, and we're here to guide you through the process.&lt;/p&gt;

&lt;p&gt;Whether you're a seasoned DevOps engineer or just starting your journey, mastering the configuration of agent pools and comprehending various pipeline styles is essential for streamlining your development workflows. Join us as we unravel the mysteries behind these integral concepts, providing insights, best practices, and actionable tips to empower your Azure DevOps journey. Let's embark on this knowledge-packed expedition together!&lt;/p&gt;

&lt;p&gt;The post will cover the creation and management of agent pools, as well as an understanding of different pipeline styles. It will also discuss the significance of these concepts in the context of DevOps practices and their impact on the efficiency of software delivery.&lt;/p&gt;

&lt;p&gt;Let start by defining the relevant concepts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure DevOps&lt;/strong&gt; &lt;br&gt;
Azure DevOps is an end-to-end software development platform that supports Agile, DevOps, and other continuous development practices. It provides a range of tools and services for requirements and project management, version control, automated builds, testing, release management, and reporting. Azure DevOps aims to help teams create and improve products at a faster pace than traditional software development approaches, fostering a collaborative culture and integrating with a variety of other tools and services. In simple terms, it's a comprehensive solution that enables teams to efficiently manage the entire software development lifecycle, from planning and coding to testing and deployment, all in one place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is agent pool and its use in Azure DevOps?&lt;/strong&gt;&lt;br&gt;
An agent pool is a collection of one or more agents that can be used to run builds and deployments in Azure DevOps. Agents are the machines that execute the tasks defined in pipelines. Agent pools are used to organize agents based on their capabilities, such as operating system, software, or hardware configurations. This allows teams to manage and allocate resources more efficiently and to ensure that the right agents are used for the right jobs. Agent pools can be created and managed from the Azure DevOps organization or project settings. They can be used in both build and release pipelines and can be either self-hosted or provided by Microsoft as hosted agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure DevOps Pipelines&lt;/strong&gt;&lt;br&gt;
Azure DevOps Pipeline is a service that allows you to automatically build, test, and deploy your code to various platforms and cloud environments. It supports continuous integration (CI) and continuous delivery (CD) practices, enabling you to automate the entire software delivery process. Azure Pipelines can be used with any language or platform, and it provides cloud-hosted agents for building and deploying applications. It offers features such as parallel jobs, native container support, and flexible deployments to Kubernetes, serverless, or virtual machines. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pipelines Configurations Supported by Azure DevOps&lt;/strong&gt;&lt;br&gt;
Azure DevOps supports YAML-based pipelines and classic UI-based pipelines configurations. The classic UI allows users to define and manage pipelines using a graphical interface, which can be a preferred option for those who are not familiar with or prefer not to use YAML syntax. However, Microsoft has been encouraging the use of YAML pipelines due to their portability, version control integration, and ease of sharing. &lt;/p&gt;

&lt;p&gt;In this exercise, we will be using the YAML-based pipeline editor, which is a tool provided by Azure DevOps to author and edit pipelines using YAML syntax.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Azure Account with subscription&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Microsoft Edge or an Azure DevOps supported browser.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set up an Azure DevOps organization&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Git for Windows &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Visual Studio Code. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Objectives&lt;/strong&gt;&lt;br&gt;
By the time you conclude this learning journey with us, you will be able to do the following in Azure DevOps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Implement YAML-based pipelines. &lt;/li&gt;
&lt;li&gt;Implement self-hosted agents.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here we go!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Setting up an Azure DevOps Organization&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Login to Azure portal at &lt;code&gt;www.portal.azure.com&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the global search bar, type in &lt;strong&gt;Azure DevOps&lt;/strong&gt; select &lt;strong&gt;Azure DevOps Organizations&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;One the Azure DevOps Home page, click on &lt;strong&gt;My Azure DevOps Organizations&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;On the &lt;strong&gt;Azure DevOps Organizations&lt;/strong&gt; page, click on &lt;strong&gt;Create new organization&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlicolj9dtpd8hccewtv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlicolj9dtpd8hccewtv.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
This will take you to a new Signup page, click on the &lt;strong&gt;Continue&lt;/strong&gt; button if you do not wish to switch directory.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;On the next page, enter a globally unique organization name, the location you intend to host your projects and the authentication character displayed.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;We can see the new organization we created and can now start creating our projects. &lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 2: Create a New Project&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Here we will be creating a project named &lt;strong&gt;eShopOnWeb&lt;/strong&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter the project name and click on &lt;strong&gt;Create project&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Our project landing page!&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Step 3: Import our project files from a Git repository to Azure DevOps&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On the project landing page, on the left pane under your project name, click on &lt;strong&gt;Repos&amp;gt;Files&lt;/strong&gt;, &lt;strong&gt;Import a Repository&lt;/strong&gt;. Select &lt;strong&gt;Import&lt;/strong&gt;. &lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;On the Import a Git Repository window, paste the URL of the Git repository where your application is located. For this exercise, we will be using a project provided by &lt;strong&gt;Microsoft&lt;/strong&gt;. Here is the URL &lt;code&gt;https://github.com/MicrosoftLearning/eShopOnWeb.git&lt;/code&gt; and click &lt;strong&gt;Import&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The project in Azure DevOps Repos Environment&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;CONFIGURE A YAML-BASED PIPELINE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Create an Azure DevOps YAML pipeline&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create a template-based Azure DevOps YAML pipeline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the EShopOnWeb project page, in the vertical navigational pane on the left side, click &lt;strong&gt;Pipelines&lt;/strong&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the &lt;strong&gt;Create Pipeline&lt;/strong&gt; button - if you don’t have any other pipelines created yet or click &lt;strong&gt;New pipeline&lt;/strong&gt; to create an additional new one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;On the &lt;strong&gt;Where is your code?&lt;/strong&gt; pane, click &lt;strong&gt;Azure Repos Git&lt;/strong&gt;. Since we have imported the code there.
_You can select other locations based on where you have your code. _&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;On the Select a repository pane, click &lt;strong&gt;EShopOnWeb&lt;/strong&gt;. Which is our project.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;On the &lt;strong&gt;Configure your pipeline&lt;/strong&gt; pane, click &lt;strong&gt;Existing Azure Pipelines YAML File&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;On the &lt;strong&gt;Select an existing YAML file&lt;/strong&gt;, select &lt;strong&gt;main&lt;/strong&gt; for the Branch, and &lt;strong&gt;/.ado/eshoponweb-ci-pr.yml&lt;/strong&gt; for the Path and click &lt;strong&gt;Continue&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;This sample pipeline is a basic .NET application Build pipeline, which does the following:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A single Stage: Build&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A single Job: Build&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;3 tasks within the Build Job:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dotnet Restore&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dotnet Build&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dotnet Publish&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

resources:
  repositories:
    - repository: self
      trigger: none

stages:
- stage: Build
  displayName: Build .Net Core Solution
  jobs:
  - job: Build
    pool:
      vmImage: ubuntu-latest
    steps:
    - task: DotNetCoreCLI@2
      displayName: Restore
      inputs:
        command: 'restore'
        projects: '**/*.sln'
        feedsToUse: 'select'

    - task: DotNetCoreCLI@2
      displayName: Build
      inputs:
        command: 'build'
        projects: '**/*.sln'

    - task: DotNetCoreCLI@2
      displayName: Test
      inputs:
        command: 'test'
        projects: 'tests/UnitTests/*.csproj'

    - task: DotNetCoreCLI@2
      displayName: Publish
      inputs:
        command: 'publish'
        publishWebProjects: true
        arguments: '-o $(Build.ArtifactStagingDirectory)'


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;On the &lt;strong&gt;Review your pipeline YAML&lt;/strong&gt; pane, click the down-facing caret symbol next to the &lt;strong&gt;Run&lt;/strong&gt; button, click &lt;strong&gt;Save&lt;/strong&gt;.
This action creates the &lt;strong&gt;pipeline&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The created pipeline&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;MANANGE AZURE DEVOPS AGENT POOLS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Configure an Azure DevOps self-hosting agent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the Azure DevOps portal, in the upper right corner of the Azure DevOps page, click the &lt;strong&gt;User settings&lt;/strong&gt; icon, depending on whether or not you have preview features turned on, you should either see a &lt;strong&gt;Security&lt;/strong&gt; or &lt;strong&gt;Personal access tokens&lt;/strong&gt; item in the menu, if you see Security, click on that, then select Personal access tokens. On the Personal Access Tokens pane, and click &lt;strong&gt;+ New Token&lt;/strong&gt;.&lt;/p&gt;

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

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

&lt;ul&gt;
&lt;li&gt;On the &lt;strong&gt;Create a new personal access token&lt;/strong&gt; pane, click the &lt;strong&gt;Show all scopes&lt;/strong&gt; link and, specify the following settings and click &lt;strong&gt;Create&lt;/strong&gt; (leave all others with their default values):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Name = EShopOnWeb&lt;/code&gt;&lt;br&gt;
&lt;code&gt;Scope (custom defined) = **Agent Pools** (show more scopes option below if needed)&lt;/code&gt;&lt;br&gt;
&lt;code&gt;Permissions = **Read and manage**&lt;/code&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;On the Success pane, copy the value of the personal access token to Clipboard and click the &lt;strong&gt;Close&lt;/strong&gt; button.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the &lt;strong&gt;Personal Access Token&lt;/strong&gt; pane of the Azure DevOps portal, click &lt;strong&gt;Azure DevOps&lt;/strong&gt; symbol in the upper left corner and then click &lt;strong&gt;Organization settings&lt;/strong&gt; label in the lower left corner.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;To the left side of the &lt;strong&gt;Overview&lt;/strong&gt; pane, in the vertical menu, in the &lt;strong&gt;Pipelines&lt;/strong&gt; section, click &lt;strong&gt;Agent pools&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;On the &lt;strong&gt;Agent pools&lt;/strong&gt; pane, in the upper right corner, click &lt;strong&gt;Add pool&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;On the &lt;strong&gt;Add agent pool&lt;/strong&gt; pane, in the &lt;strong&gt;Pool type&lt;/strong&gt; dropdown list, select *&lt;strong&gt;*Self-hosted, in the Name text box, type **proj-2420-pool&lt;/strong&gt; and then click &lt;strong&gt;Create&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Back on the Agent pools pane, click the entry representing the newly created &lt;strong&gt;proj-2420-pool&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the Jobs tab of the &lt;strong&gt;proj-2420-pool&lt;/strong&gt; pane, click the &lt;strong&gt;New agent&lt;/strong&gt; button.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;On the &lt;strong&gt;Get the agent&lt;/strong&gt; pane, ensure that the &lt;strong&gt;Windows&lt;/strong&gt; and &lt;strong&gt;x64&lt;/strong&gt; tabs are selected, and click &lt;strong&gt;Download&lt;/strong&gt; to download the zip archive containing the agent binaries to download it into the local **Downloads*8 folder within your user profile.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;To Register the Agent&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start Windows PowerShell as administrator and in the &lt;strong&gt;Administrator: Windows PowerShell&lt;/strong&gt; console run the following lines to create the &lt;strong&gt;C:\agent&lt;/strong&gt; directory and extract the content of the downloaded archive into it. By running these lines of code:&lt;/li&gt;
&lt;/ul&gt;

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

cd \
mkdir agent ; cd agent
$TARGET = Get-ChildItem "$Home\Downloads\vsts-agent-win-x64-*.zip"
Add-Type -AssemblyName System.IO.Compression.FileSystem



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

&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;In the same &lt;strong&gt;Administrator: Windows PowerShell&lt;/strong&gt; console, run the following to configure the agent:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;.\config.cmd&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;-Follow the prompt, specify the values of the following settings:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Enter server URL = https://dev.azure.com/Project-24201&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's the URL of your Azure DevOps organization, in the format &lt;a href="https://dev.azure.com/**organization_name**" rel="noopener noreferrer"&gt;https://dev.azure.com/**organization_name**&lt;/a&gt;, where &lt;strong&gt;organization_name&lt;/strong&gt; represents the name of your Azure DevOps organization.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Enter authentication type (press enter for PAT) = **Enter**&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Enter personal access token = **The access token you recorded earlier in this task**&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Enter agent pool (press enter for default) = **proj-2420-pool**&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Enter agent name = **az400m03-vm0**&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Enter work folder (press enter for _work) = **Enter**&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;(Only if shown) Enter Perform an unzip for tasks for each step. (press enter for N)   WARNING: only press **Enter** if the message is shown&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If the above prompt is not displayed, then continue with your configuration.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Enter run agent as service? (Y/N) (press enter for N) = **Y**&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;enter enable SERVICE_SID_TYPE_UNRESTRICTED (Y/N) (press enter for N) = **Y**&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Enter User account to use for the service (press enter for NT AUTHORITY\NETWORK SERVICE) = **Enter**&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Enter whether to prevent service starting immediately after configuration is finished? (Y/N) (press enter for N) = **Enter**&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At the end of registering the agent, you will get successful notification as shown below.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Back to the Azure DevOps portal&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the web browser window displaying the Azure DevOps portal, in the upper left corner, click the &lt;strong&gt;Azure DevOps&lt;/strong&gt; label.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;From the list of projects, click the tile representing your &lt;strong&gt;EShopOnWeb&lt;/strong&gt; project.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;On the &lt;strong&gt;EShopOnWeb&lt;/strong&gt; pane, in the vertical navigational pane on the left side, in the &lt;strong&gt;Pipelines&lt;/strong&gt; section, click &lt;strong&gt;Pipelines&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;On the &lt;strong&gt;Recent&lt;/strong&gt; tab of the &lt;strong&gt;Pipelines&lt;/strong&gt; pane, select &lt;strong&gt;EShopOnWeb&lt;/strong&gt; and, on the &lt;strong&gt;EShopOnWeb&lt;/strong&gt; pane, select &lt;strong&gt;Edit&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Here we provide the detail of the new agent we registered to the &lt;strong&gt;YAML-based CI&lt;/strong&gt; pipeline. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the &lt;strong&gt;EShopOnWeb&lt;/strong&gt; edit pane, in the existing YAML-based pipeline, replace line 13 which says &lt;strong&gt;vmImage: ubuntu-latest&lt;/strong&gt; designating the target agent pool with the newly created self-hosted agent pool.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;On the &lt;strong&gt;EShopOnWeb&lt;/strong&gt; edit pane, in the upper right corner of the pane, click &lt;strong&gt;Save and Run&lt;/strong&gt;. This will automatically trigger the build based on this pipeline.&lt;/li&gt;
&lt;/ul&gt;

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

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

&lt;p&gt;You can check the option to &lt;strong&gt;Enable system diagnostic&lt;/strong&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;In the Azure DevOps portal, in the vertical navigational pane on the left side, in the Pipelines section, click &lt;strong&gt;Pipelines&lt;/strong&gt;. Depending on your system setup, the pipeline might prompt you for permissions. Click &lt;strong&gt;Permit&lt;/strong&gt; to allow the pipeline to run.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
On the &lt;strong&gt;Recent&lt;/strong&gt; tab of the &lt;strong&gt;Pipelines&lt;/strong&gt; pane, click the &lt;strong&gt;EShopOnWeb&lt;/strong&gt; entry, on the &lt;strong&gt;Runs&lt;/strong&gt; tab of the &lt;strong&gt;EShopOnWeb&lt;/strong&gt; pane, select the most recent run, on the &lt;strong&gt;Summary&lt;/strong&gt; pane of the run, scroll down to the bottom, in the &lt;strong&gt;Jobs&lt;/strong&gt; section, click &lt;strong&gt;Phase 1&lt;/strong&gt; and monitor the job until its successful completion.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;When the process concludes, you will be notified of the status of your pipeline as successfully built.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Thank you for your time, kindly subscribe and follow our page for more insightful articles.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>devops</category>
      <category>cloudcomputing</category>
      <category>microservices</category>
    </item>
    <item>
      <title>S3crets Unveiled: A Painless Guide to Hosting Your First Static Website on AWS.</title>
      <dc:creator>Bernard Chika Uwaezuoke</dc:creator>
      <pubDate>Fri, 02 Feb 2024 21:22:36 +0000</pubDate>
      <link>https://dev.to/donhadley22/s3crets-unveiled-a-painless-guide-to-hosting-your-first-static-website-on-aws-5i3</link>
      <guid>https://dev.to/donhadley22/s3crets-unveiled-a-painless-guide-to-hosting-your-first-static-website-on-aws-5i3</guid>
      <description>&lt;p&gt;&lt;strong&gt;INTRODUCTION&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Welcome to the enchanting realm of Cloud Hosting! If the mere thought of launching a website feels like deciphering an alien language, fear not. In this step-by-step journey, we'll unravel the mystery of hosting a static website on AWS S3, transforming the seemingly complex into a seamless symphony of digital creation. Picture it as your backstage pass to the CloudCraft Chronicles, where we'll guide you with clarity, unraveling the S3crets to effortlessly elevate your web presence. Embrace the magic of simplicity as we embark on this adventure together, turning novices into cloud virtuosos!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PREREQUISITES&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS Account:&lt;/strong&gt; Ensure you have an active AWS account. If you don't have one yet, fear not! You can do that for free on &lt;br&gt;
&lt;code&gt;www.aws.amazon.com&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic Web Files:&lt;/strong&gt; Have your static website files ready. Whether it's a portfolio, blog, or a simple landing page, make sure your HTML, CSS, and other assets are set to go.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Curiosity and Eagerness:&lt;/strong&gt; The most important prerequisites! Bring along your curiosity and eagerness to learn. We're here to make this journey enjoyable and enlightening, even for the absolute beginner.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Now that our toolkit is prepped, let's set the stage for our CloudCraft Chronicles and unlock the door to AWS S3 hosting magic!&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEP 1: login to your AWS account&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to your web browser and enter this URL: &lt;code&gt;https://aws.amazon.com/console/&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on &lt;strong&gt;sign in to the console&lt;/strong&gt; button.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmdw2axi5cis16lj4938p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmdw2axi5cis16lj4938p.png" alt="Image description" width="800" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Select to sign in as a &lt;strong&gt;Root user&lt;/strong&gt; if you own the account, otherwise, if given access by an organization or a third party, the select &lt;strong&gt;IAM user&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Provide your accout email and click the &lt;strong&gt;Next&lt;/strong&gt; button.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7jkbzaez1rosou67w4fg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7jkbzaez1rosou67w4fg.png" alt="Image description" width="571" height="588"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide your password and click on &lt;strong&gt;sign in&lt;/strong&gt; button.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fky4j16x5tvl7bq0fbnbk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fky4j16x5tvl7bq0fbnbk.png" alt="Image description" width="555" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEP 2: Creating an S3 Bucket&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On the global search bar at the top of your home page, type &lt;strong&gt;s3&lt;/strong&gt; and select &lt;strong&gt;s3&lt;/strong&gt; from the auto-dropdown. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frcop45r3ibg446wzmac0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frcop45r3ibg446wzmac0.png" alt="Image description" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On the &lt;strong&gt;S3&lt;/strong&gt; page, click on &lt;strong&gt;Create bucket&lt;/strong&gt; button.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fomxfauo76pazrtls4ebm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fomxfauo76pazrtls4ebm.png" alt="Image description" width="800" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;On the box &lt;strong&gt;AWS Region&lt;/strong&gt; box, dropdown and select the region you want to host your static website.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Leave it in &lt;strong&gt;General purpose&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then enter a globally unique bucket name in the box &lt;strong&gt;Bucket name&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpr545kqjn36369dn6cin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpr545kqjn36369dn6cin.png" alt="Image description" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Scroll down on same page.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Leave the default &lt;strong&gt;ACL disabled&lt;/strong&gt; option.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Uncheck the &lt;strong&gt;Block all public access&lt;/strong&gt; option.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz6no8dh631hz0buinlbi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz6no8dh631hz0buinlbi.png" alt="Image description" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A warning massage will be disblayed intimating you of the implication of making the bucket public, acknowledge it, leave other configurations as default and continue to the base of the page to click on the &lt;strong&gt;Create bucket&lt;/strong&gt; button.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvw5p16uiotzr9ly77tvf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvw5p16uiotzr9ly77tvf.png" alt="Image description" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdl43dt6278fkhh97vul4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdl43dt6278fkhh97vul4.png" alt="Image description" width="800" height="343"&gt;&lt;/a&gt;&lt;br&gt;
 &lt;em&gt;The created &lt;strong&gt;project-2420&lt;/strong&gt; S3 bucket and message confirming its successful creation.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEP 3: Uploading our Static web files to the S3 Bucket&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Click on the created bucket &lt;strong&gt;project-2420&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the bucket page, click on &lt;strong&gt;Upload&lt;/strong&gt; button. This will open a pane that you can use to navigate to your resources in your local environment.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuz7p77w4h4g254svutay.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuz7p77w4h4g254svutay.png" alt="Image description" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select &lt;strong&gt;add files&lt;/strong&gt; or &lt;strong&gt;add folder&lt;/strong&gt; depending on how your web resources are saved.
For this demonstration, we will be using &lt;strong&gt;add folder&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fagtn70wmlgvo2pxpyud2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fagtn70wmlgvo2pxpyud2.png" alt="Image description" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We will be uploading our resources from our local desktop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3o7uazgb3zpi2r8npjl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3o7uazgb3zpi2r8npjl.png" alt="Image description" width="668" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5bxefq0ji38m8mxjcr8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5bxefq0ji38m8mxjcr8.png" alt="Image description" width="800" height="501"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The uploaded files&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;index.html&lt;/strong&gt; and the &lt;strong&gt;error.html&lt;/strong&gt; or in this instance &lt;strong&gt;404.html&lt;/strong&gt;. They are the primary pages that display on the website.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scroll to the bottom of the page and click the &lt;strong&gt;Upload&lt;/strong&gt; button.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwboefl6xgu3rd3d6a5m6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwboefl6xgu3rd3d6a5m6.png" alt="Image description" width="800" height="527"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwmm2qa6qotfj3wyaw5kc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwmm2qa6qotfj3wyaw5kc.png" alt="Image description" width="800" height="328"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Files uploaded successfully.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEP 4: Configure the Static Website&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We go back to the &lt;strong&gt;project-2420&lt;/strong&gt; S3 bucket page by clicking on &lt;strong&gt;Close&lt;/strong&gt; button and click on the &lt;strong&gt;permission&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9te0saot7qf0nr83rapy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9te0saot7qf0nr83rapy.png" alt="Image description" width="800" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scroll down on the opened page and click on &lt;strong&gt;Edit&lt;/strong&gt; button by &lt;strong&gt;Bucket policy&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frn5gk7iurd9vtz3ul1qg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frn5gk7iurd9vtz3ul1qg.png" alt="Image description" width="800" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input the policy below in the editor with your bucket name replacing the place holder. (We got the policy from AWS documentation on S3 static website hosting)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::Bucket-Name/*"
            ]
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kicspow6on4mh7fnv5t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kicspow6on4mh7fnv5t.png" alt="Image description" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Click on &lt;strong&gt;Save changes&lt;/strong&gt; button at the base of the page.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You will get a notification that the policy change was successful.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The click on &lt;strong&gt;property&lt;/strong&gt; on the same &lt;strong&gt;project-2420&lt;/strong&gt; bucket page.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2jg4nd3zjegla1mv4n11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2jg4nd3zjegla1mv4n11.png" alt="Image description" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scroll down to the base of the page and on &lt;strong&gt;Static website hosting&lt;/strong&gt;, click &lt;strong&gt;Edit&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feeksvtkb9e11bsohp67k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feeksvtkb9e11bsohp67k.png" alt="Image description" width="800" height="109"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select &lt;strong&gt;Enable&lt;/strong&gt; option for &lt;strong&gt;Static website&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This will extend the pane, exposing boxes for you to input your &lt;strong&gt;index.html&lt;/strong&gt; and &lt;strong&gt;error.html&lt;/strong&gt; configurations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Leave other configurations as default and click &lt;strong&gt;Save Changes&lt;/strong&gt; button at the base of the page.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You will get this notification of success.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft677qrhh7ru8yjzqhq8t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft677qrhh7ru8yjzqhq8t.png" alt="Image description" width="800" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At the base of the page, we can see our static website bucket endpoint which we can use to access it over a browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1b631f5wkbelejafb2wa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1b631f5wkbelejafb2wa.png" alt="Image description" width="800" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copy the endpoint and use it to view the static website.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw47a4uxs2robuoa2r04k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw47a4uxs2robuoa2r04k.png" alt="Image description" width="800" height="434"&gt;&lt;/a&gt;&lt;br&gt;
               &lt;em&gt;The webpage displayed&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cleanup to avoid careless Charges&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Click on the &lt;strong&gt;Action&lt;/strong&gt; button on the &lt;strong&gt;project-2420&lt;/strong&gt; bucket page. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select empty bucket, enter &lt;strong&gt;permanently delete&lt;/strong&gt; in the provided box and click the &lt;strong&gt;Empty&lt;/strong&gt; button.&lt;br&gt;
You will get a notification indicating that the content of the bucket has been deleted.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then click on the &lt;strong&gt;delete&lt;/strong&gt; button on the &lt;strong&gt;project-2420&lt;/strong&gt; bucket page. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You will be prompted to enter the bucket name in the provided box. This action will activate the &lt;strong&gt;Delete bucket&lt;/strong&gt; button. Click on the &lt;strong&gt;Delete bucket&lt;/strong&gt; button and the bucket will be deleted.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjn2zctkhsd6cph96hhp4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjn2zctkhsd6cph96hhp4.png" alt="Image description" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CONCLUSION&lt;/strong&gt;&lt;br&gt;
As our CloudCraft Chronicles come to a close, take a moment to marvel at the digital masterpiece you've crafted in the AWS S3 cloudscape. From a novice navigating the unknown, you've emerged as a virtual virtuoso, orchestrating your static website with finesse.&lt;/p&gt;

&lt;p&gt;Remember, the magic of hosting extends beyond technology; it's about empowering your ideas to soar in the vast expanse of the internet. Whether you're a hobbyist, entrepreneur, or dream-weaver, your corner of the web is now a reality.&lt;/p&gt;

&lt;p&gt;As you embark on your digital journey, may the CloudCraft Chronicles be a guiding star, illuminating the path to endless possibilities. The curtain may fall on this blog, but your story in the cloud has just begun. &lt;/p&gt;

&lt;p&gt;Until our next adventure, happy hosting, and may your digital dreams continue to unfold!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please subscribe and follow our blog&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>staticwebapps</category>
      <category>s3</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Building a Custom VPC Infrastructure in AWS with Terraform.</title>
      <dc:creator>Bernard Chika Uwaezuoke</dc:creator>
      <pubDate>Tue, 16 Jan 2024 04:19:55 +0000</pubDate>
      <link>https://dev.to/donhadley22/building-a-custom-vpc-infrastructure-in-aws-with-terraform-p3j</link>
      <guid>https://dev.to/donhadley22/building-a-custom-vpc-infrastructure-in-aws-with-terraform-p3j</guid>
      <description>&lt;p&gt;&lt;strong&gt;INTRODUCTION&lt;/strong&gt;&lt;br&gt;
Amazon Web Services (AWS) provides a robust set of tools to manage cloud infrastructure, and Terraform enhances this capability by enabling Infrastructure as Code (IaC). In this article, we will explore how to use Terraform to create a custom &lt;strong&gt;VPC (Virtual Private Cloud)&lt;/strong&gt; in AWS, complete with an &lt;strong&gt;internet gateway&lt;/strong&gt;, a &lt;strong&gt;route table&lt;/strong&gt;, &lt;strong&gt;public&lt;/strong&gt; and &lt;strong&gt;private subnets&lt;/strong&gt;, &lt;strong&gt;security groups&lt;/strong&gt;, &lt;strong&gt;network interfaces&lt;/strong&gt;, and &lt;strong&gt;EC2 instances&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PREREQUISITES&lt;/strong&gt;&lt;br&gt;
Before embarking on the journey of creating a custom VPC infrastructure in AWS using Terraform, ensure you have the following in place:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. AWS Account:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Create an AWS account if you don't have one already. Navigate to the AWS Console at &lt;code&gt;www.aws.amazon.com&lt;/code&gt; to set up your account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. AWS CLI and Credentials:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Install the AWS Command Line Interface (CLI) on your local machine. You can download it here.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure your AWS credentials using the &lt;code&gt;aws configure&lt;/code&gt; command.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure your credentials have the necessary permissions to create and manage resources.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3.Terraform Installation:&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Download and install the latest version of Terraform on your local machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Text Editor or Integrated Development Environment (IDE):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Choose a text editor or IDE of your preference for editing Terraform configuration files. Popular choices include VSCode, Sublime Text, or Atom.&lt;br&gt;
&lt;em&gt;(For this demonstration, we will be using VSCode.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Basic Understanding of AWS Services:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Familiarize yourself with basic AWS concepts, including &lt;strong&gt;VPC&lt;/strong&gt;, &lt;strong&gt;subnetting&lt;/strong&gt;, &lt;strong&gt;internet gateways&lt;/strong&gt;, &lt;strong&gt;security groups&lt;/strong&gt;, and &lt;strong&gt;EC2 instances&lt;/strong&gt;. This foundational knowledge will help you design an infrastructure that meets your specific requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;6. SSH Key Pair:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Generate an SSH key pair if you plan to access your EC2 instances. This key pair will be used when creating EC2 instances in your Terraform configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;7. Knowledge of Terraform Basics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Understand the fundamental concepts of Terraform, such as providers, resources, variables, and modules. Refer to the official Terraform documentation at &lt;code&gt;https://developer.hashicorp.com/terraform/docs&lt;/code&gt; for guidance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;8. Customization:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Modify the provided Terraform configuration according to your specific needs. Update variables like &lt;strong&gt;cidr_block&lt;/strong&gt;, &lt;strong&gt;availability_zone&lt;/strong&gt;, &lt;strong&gt;key_name&lt;/strong&gt;, and &lt;strong&gt;ami&lt;/strong&gt; with values suitable for your project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;9. Security Considerations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Be mindful of security best practices. Restrict inbound and outbound traffic in your security groups to only the necessary ports and IP ranges.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Let dive right in!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEP 1: CREATE YOUR WORKING DIRECTORY AND CONFIGURATION FILES&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to your &lt;strong&gt;Git Bash&lt;/strong&gt; interface and type this command &lt;code&gt;cd Desktop&lt;/code&gt; to navigate to the location you want to create the folder. In this very instance, we are creating it at the &lt;strong&gt;Desktop&lt;/strong&gt;.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a Directory (folder). Type the following command &lt;code&gt;mkdir &amp;lt;folder name&amp;gt;&lt;/code&gt;to create the directory.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftr4b2znhh4jsn8c44t8k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftr4b2znhh4jsn8c44t8k.png" alt="Image description" width="672" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Navigate to the directory you just created using the &lt;code&gt;cd command&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;While in the directory, use the command &lt;code&gt;code .&lt;/code&gt; to open the VScode IDE. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv1hu9ibhgiaurlumk3wx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv1hu9ibhgiaurlumk3wx.png" alt="Image description" width="773" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgw2te1mtmv4zhuh450dt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgw2te1mtmv4zhuh450dt.png" alt="Image description" width="290" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Create'main.tf' and &lt;code&gt;provider.tf&lt;/code&gt; files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwbb3vev49ommq2r8k327.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwbb3vev49ommq2r8k327.png" alt="Image description" width="684" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEP 2: CONFIGURE AWS ACCESS AND PROVIDER&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Run the following commands: &lt;code&gt;aws --version&lt;/code&gt; to confirm your version of &lt;strong&gt;aws-cli&lt;/strong&gt; and &lt;code&gt;aws configure&lt;/code&gt; to provide your AWS credentials in your &lt;strong&gt;VScode terminal&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The credentials:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS Access Key ID [****************ZYAS]:
AWS Secret Access Key [****************6H9/]: 
Default region name [us-east-1]: 
Default output format [json]: 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqwnnnaf0gcpvnbhgdea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqwnnnaf0gcpvnbhgdea.png" alt="Image description" width="779" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to the official Terraform site &lt;code&gt;https://registry.terraform.io/providers/hashicorp/aws/latest/docs&lt;/code&gt; to get your Terraform codes to customize for your deployment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the &lt;strong&gt;provider.tf&lt;/strong&gt; file, paste the &lt;strong&gt;AWS Provider&lt;/strong&gt; configuration code, then save.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&amp;gt; 5.0"
    }
  }
}

# Configure the AWS Provider
provider "aws" {
  region = "us-east-1"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpyh5syorgvg5jr226l90.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpyh5syorgvg5jr226l90.png" alt="Image description" width="326" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Then run the &lt;code&gt;terraform init&lt;/code&gt; command to initialize Terraform. This command will create a folder containing terraform plugins and a &lt;code&gt;.terraform.lock.hcl&lt;/code&gt; file, indicating that your environment is ready to apply your deployments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flfg2mgykckfvczmy3t1b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flfg2mgykckfvczmy3t1b.png" alt="Image description" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEP 3 DEFINE THE VPC, INTERNET GATEWAY, AND ROUTE TABLE&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to the &lt;code&gt;main.tf&lt;/code&gt; file and start declaring the resources you want provisioned using the &lt;strong&gt;HashiCorp Configuration Language (HCL)&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create a VPC
resource "aws_vpc" "main-vpc" {
  cidr_block       = "10.0.0.0/16"
  instance_tenancy = "default"

  tags = {
    Name = "main-vpc"
  }
}

# Create an Internet Gateway
resource "aws_internet_gateway" "classgw" {
  vpc_id = aws_vpc.main-vpc.id

  tags = {
    Name = "classgw"
  }
}

# Create a Route Table
resource "aws_route_table" "classRT" {
  vpc_id = aws_vpc.main-vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.classgw.id
  }
}

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

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6niikbb17owrir3h682h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6niikbb17owrir3h682h.png" alt="Image description" width="789" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Run the commands &lt;code&gt;terraform init&lt;/code&gt;, &lt;code&gt;terraform validate&lt;/code&gt;, &lt;code&gt;terraform plan&lt;/code&gt; and &lt;code&gt;terraform apply&lt;/code&gt; in sequence to provision the resources. You can choose to do this in batches, as we are doing here, or run the commands after declaring all your resources.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Output of &lt;code&gt;terraform plan&lt;/code&gt; command&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_internet_gateway.classgw will be created        
  + resource "aws_internet_gateway" "classgw" {
      + arn      = (known after apply)
      + id       = (known after apply)
      + owner_id = (known after apply)
      + tags     = {
          + "Name" = "classgw"
        }
      + tags_all = {
          + "Name" = "classgw"
        }
      + vpc_id   = (known after apply)
    }

  # aws_route_table.classRT will be created
  + resource "aws_route_table" "classRT" {
      + arn              = (known after apply)
      + id               = (known after apply)
      + owner_id         = (known after apply)
      + propagating_vgws = (known after apply)
      + route            = [
          + {
              + carrier_gateway_id         = ""
              + cidr_block                 = "0.0.0.0/0"
              + core_network_arn           = ""
              + destination_prefix_list_id = ""
              + egress_only_gateway_id     = ""
              + gateway_id                 = (known after apply)
              + ipv6_cidr_block            = ""
              + local_gateway_id           = ""
              + nat_gateway_id             = ""
              + network_interface_id       = ""
              + transit_gateway_id         = ""
              + vpc_endpoint_id            = ""
              + vpc_peering_connection_id  = ""
            },
        ]
      + tags_all         = (known after apply)
      + vpc_id           = (known after apply)
    }

  # aws_vpc.main-vpc will be created
  + resource "aws_vpc" "main-vpc" {
      + arn                                  = (known after apply)
      + cidr_block                           = "10.0.0.0/16"
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + default_security_group_id            = (known after apply)
      + dhcp_options_id                      = (known after apply)
      + enable_dns_hostnames                 = (known after apply)
      + enable_dns_support                   = true
      + enable_network_address_usage_metrics = (known after apply)
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags                                 = {
          + "Name" = "main-vpc"
        }
      + tags_all                             = {
          + "Name" = "main-vpc"
        }
    }

Plan: 3 to add, 0 to change, 0 to destroy.

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Output of &lt;code&gt;terraform plan&lt;/code&gt; command&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_internet_gateway.classgw will be created
  + resource "aws_internet_gateway" "classgw" {
      + arn      = (known after apply)
      + id       = (known after apply)
      + owner_id = (known after apply)
      + tags     = {
          + "Name" = "classgw"
        }
      + tags_all = {
          + "Name" = "classgw"
        }
      + vpc_id   = (known after apply)
    }

  # aws_route_table.classRT will be created
  + resource "aws_route_table" "classRT" {
      + arn              = (known after apply)
      + id               = (known after apply)
      + owner_id         = (known after apply)
      + propagating_vgws = (known after apply)
      + route            = [
          + {
              + carrier_gateway_id         = ""
              + cidr_block                 = "0.0.0.0/0"
              + core_network_arn           = ""
              + destination_prefix_list_id = ""
              + egress_only_gateway_id     = ""
              + gateway_id                 = (known after apply)
              + ipv6_cidr_block            = ""
              + local_gateway_id           = ""
              + nat_gateway_id             = ""
              + network_interface_id       = ""
              + transit_gateway_id         = ""
              + vpc_endpoint_id            = ""
              + vpc_peering_connection_id  = ""
            },
        ]
      + tags_all         = (known after apply)
      + vpc_id           = (known after apply)
    }

  # aws_vpc.main-vpc will be created
  + resource "aws_vpc" "main-vpc" {
      + arn                                  = (known after apply)
      + cidr_block                           = "10.0.0.0/16"
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + default_security_group_id            = (known after apply)
      + dhcp_options_id                      = (known after apply)
      + enable_dns_hostnames                 = (known after apply)
      + enable_dns_support                   = true
      + enable_network_address_usage_metrics = (known after apply)
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags                                 = {
          + "Name" = "main-vpc"
        }
      + tags_all                             = {
          + "Name" = "main-vpc"
        }
    }

Plan: 3 to add, 0 to change, 0 to destroy.
aws_vpc.main-vpc: Creating...
aws_vpc.main-vpc: Creation complete after 5s [id=vpc-07733c710c0a92868]
aws_internet_gateway.classgw: Creating...
aws_internet_gateway.classgw: Creation complete after 2s [id=igw-08480f88ccaf9b886]
aws_route_table.classRT: Creating...
aws_route_table.classRT: Creation complete after 2s [id=rtb-0bed4113ba3664e06]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We will repeat the above steps until we finish provisioning our resources.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The provisioned resources in AWS management console&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd47kppz9om85h2cl441z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd47kppz9om85h2cl441z.png" alt="Image description" width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fta59qop1hqjfj39nbzbu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fta59qop1hqjfj39nbzbu.png" alt="Image description" width="800" height="149"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgwol6gypbf2bsm89eun.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgwol6gypbf2bsm89eun.png" alt="Image description" width="800" height="179"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEP 4: CREATE PUBLIC AND PRIVATE SUBNETS&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On the &lt;code&gt;main.tf&lt;/code&gt; file, go ahead and declare the commands for the subnets to provision both &lt;strong&gt;public&lt;/strong&gt; and &lt;strong&gt;private subnets&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create public subnets
resource "aws_subnet" "main_pubs1" {
  vpc_id            = aws_vpc.main-vpc.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "us-east-1a"

  tags = {
    Name = "main_pubs1"
  }
}

resource "aws_subnet" "main_pubs2" {
  vpc_id            = aws_vpc.main-vpc.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "us-east-1b"

  tags = {
    Name = "main_pubs2"
  }
}

# Create private subnets
resource "aws_subnet" "main_privs1" {
  vpc_id            = aws_vpc.main-vpc.id
  cidr_block        = "10.0.3.0/24"
  availability_zone = "us-east-1c"

  tags = {
    Name = "main_privs1"
  }
}

resource "aws_subnet" "main_privs2" {
  vpc_id            = aws_vpc.main-vpc.id
  cidr_block        = "10.0.4.0/24"
  availability_zone = "us-east-1d"

  tags = {
    Name = "main_privs2"
  }
}

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

&lt;/div&gt;


&lt;p&gt;In this demonstration, we have two public and two private subnets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEP 5: ASSOCIATE SUBNETS WITH THE ROUTE TABLE&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Associate public subnets with the route table
resource "aws_route_table_association" "a" {
  subnet_id      = aws_subnet.main_pubs1.id
  route_table_id = aws_route_table.classRT.id
}

resource "aws_route_table_association" "b" {
  subnet_id      = aws_subnet.main_pubs2.id
  route_table_id = aws_route_table.classRT.id
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;STEP 6:  CREATE A SECURITY GROUP&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create a security group
resource "aws_security_group" "class_SG" {
  name        = "class_SG"
  description = "Allow SSH, HTTP, HTTPS inbound traffic"
  vpc_id      = aws_vpc.main-vpc.id

  ingress {
    description = "SSH from VPC"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "HTTPS from VPC"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "HTTP from VPC"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
     from_port   = 0
     to_port     = 0
     protocol    = "-1"
     cidr_blocks = ["0.0.0.0/0"]
   }

  tags = {
    Name = "allow_traffic"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;STEP 7: CREATE NETWORK INTERFACES&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create network interfaces for the two instances.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create network interfaces
resource "aws_network_interface" "class_NI" {
  subnet_id       = aws_subnet.main_pubs1.id
  private_ips     = ["10.0.1.50"]
  security_groups = [aws_security_group.class_SG.id]
}

resource "aws_network_interface" "main-NIC" {
  subnet_id       = aws_subnet.main_pubs2.id
  private_ips     = ["10.0.2.20"]
  security_groups = [aws_security_group.class_SG.id]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;STEP 8: ALLOCATE ELASTIC IPS AND ASSOCIATE WITH NETWORK INTERFACES&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create Elastic IPs
resource "aws_eip" "class_EIP" {
  vpc                       = true
  network_interface         = aws_network_interface.class_NI.id
  associate_with_private_ip = "10.0.1.50"
}

resource "aws_eip" "class_EIP1" {
  vpc                       = true
  network_interface         = aws_network_interface.main-NIC.id
  associate_with_private_ip = "10.0.2.20"
}

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;STEP 9: LAUNCH TWO EC2 INSTANCES&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One in a Public Subnet and the other in a Private Subnet.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create EC2 instances
resource "aws_instance" "class_instance" {
  ami           = "ami-0005e0cfe09cc9050"
  instance_type = "t2.micro"
  key_name      = "Don-KP"
  network_interface {
    network_interface_id = aws_network_interface.class_NI.id
    device_index         = 0
  }

  tags = {
    Name = "hello1"
  }
}

resource "aws_instance" "class_instance222" {
  ami           = "ami-0005e0cfe09cc9050"
  instance_type = "t2.micro"
  key_name      = "Don-KP"
  network_interface {
    network_interface_id = aws_network_interface.main-NIC.id
    device_index         = 0
  }

  tags = {
    Name = "hello2"
  }
}

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Resources Created&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftm7twvmu19mruefgrzg3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftm7twvmu19mruefgrzg3.png" alt="Image description" width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwikd8l2dt6kyddp0qxsm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwikd8l2dt6kyddp0qxsm.png" alt="Image description" width="800" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8p10i8qr8nzd673ip05s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8p10i8qr8nzd673ip05s.png" alt="Image description" width="800" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3u3uz8r71pku5kxp9wm4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3u3uz8r71pku5kxp9wm4.png" alt="Image description" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77f4nmzaqpyor7n1ti94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77f4nmzaqpyor7n1ti94.png" alt="Image description" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STEP 10: DESTROY&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the command &lt;code&gt;terraform destroy&lt;/code&gt; to clean-up the resources provisioned to have a clean slate and avoid paying for resources not used for production. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;CONCLUSION&lt;/strong&gt;&lt;br&gt;
In this tutorial, we've walked through the process of creating a custom VPC infrastructure in AWS using Terraform. This modular approach allows for easy maintenance, scalability, and reproducibility of your cloud environment. By leveraging Terraform, you gain the benefits of Infrastructure as Code, making it simpler to manage and collaborate on complex AWS architectures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thank you for your time!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>terraform</category>
      <category>cloudcomputing</category>
      <category>infrastructureascode</category>
    </item>
    <item>
      <title>Connecting the Dots: A Step-by-Step Guide to Azure VNet Peering with Azure CLI.</title>
      <dc:creator>Bernard Chika Uwaezuoke</dc:creator>
      <pubDate>Thu, 19 Oct 2023 23:23:29 +0000</pubDate>
      <link>https://dev.to/donhadley22/connecting-the-dots-a-step-by-step-guide-to-azure-vnet-peering-with-azure-cli-5529</link>
      <guid>https://dev.to/donhadley22/connecting-the-dots-a-step-by-step-guide-to-azure-vnet-peering-with-azure-cli-5529</guid>
      <description>&lt;h2&gt;
  
  
  INTRODUCTION
&lt;/h2&gt;

&lt;p&gt;In today's interconnected digital landscape, the ability to create secure and seamless connections between different components of your cloud infrastructure is paramount.  Azure Virtual Network (VNet) peering, one of the many powerful features offered by Microsoft Azure, enables you to achieve just that. With VNet peering, you can establish network communication between Virtual Networks in Azure, creating a unified and efficient network environment for your applications and services.&lt;/p&gt;

&lt;p&gt;But, as with any sophisticated technology, getting started with Azure VNet peering may seem daunting at first. That's where we come in! In this comprehensive guide, we'll walk you through the step-by-step process of implementing Azure VNet peering using Azure CLI, the command-line interface that offers precise control and automation capabilities for Azure services.&lt;/p&gt;

&lt;p&gt;Whether you're an Azure newcomer looking to understand the fundamentals or an experienced cloud architect seeking a refresher, this blog will provide you with the insights and practical instructions you need to create, configure, and manage Azure VNet peering like a pro.&lt;/p&gt;

&lt;p&gt;Join us as we demystify the world of Virtual Network peering, exploring the key concepts, prerequisites, and best practices. By the end of this guide, you'll be equipped with the knowledge and tools to effortlessly establish network connections across your Azure VNets, enabling your applications and services to communicate seamlessly.&lt;/p&gt;

&lt;p&gt;So, without further ado, let's dive into the exciting world of Azure VNet peering with Azure CLI and take the first step toward creating an interconnected, efficient, and resilient cloud infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  PREREQUISITES
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Azure Subscription&lt;/li&gt;
&lt;li&gt;Azure CLI&lt;/li&gt;
&lt;li&gt;Planning and Design&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;STEP 1: Sign into Azure Account&lt;/strong&gt; &lt;br&gt;
Login to your &lt;strong&gt;Azure account&lt;/strong&gt; through your &lt;strong&gt;Command Line Interface (CLI)&lt;/strong&gt; by typing this command &lt;code&gt;az login&lt;/code&gt; as shown in the image below then tap the &lt;strong&gt;enter&lt;/strong&gt; key to run it.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;This opens a tab in your browser, where you will be required to provide your login details.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Select the particular account you wish to deploy the resources and sign in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A notification indicating you have been granted access to the account displays.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Your account detail is displayed on your CLI.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;STEP 2: Create a Resource Group&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A resource group is a logical grouping of Azure resources that are related to a specific application, project, or environment. It serves as a logical container for our resources in Azure and helps us in &lt;strong&gt;organization, management, access control, deletion and cleanup of our account.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We create the &lt;strong&gt;resource group&lt;/strong&gt; using this command:&lt;br&gt;
&lt;code&gt;az group create --name &amp;lt;resource group name&amp;gt; --location &amp;lt;azure region&amp;gt;&lt;/code&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;From the above image, it can be seen that for this exercise, we created a resource group called &lt;strong&gt;Proj-RG&lt;/strong&gt;. Which is visible on the portal (see below).&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;STEP 3: Create the Virtual Networks (Vnet).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this exercise, we will be creating two virtual networks (&lt;strong&gt;EastVNet&lt;/strong&gt; and &lt;strong&gt;WestVNet&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;We proceed to create the virtual networks that we intend to peer. The convention is to have distinct IP address range for each Vnet to ensure that they do not overlap. Virtual networks that are to be peered should not have overlapping IP addresses.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We use this command to create the Vnets one after the other. &lt;br&gt;
&lt;code&gt;az network vnet create --resource-group &amp;lt;resource group name&amp;gt; --name &amp;lt;Vnet name&amp;gt; --address-prefix 10.1.0.0/16 --subnet-name &amp;lt;subnet name&amp;gt; --subnet-prefix 10.1.0.0/24&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For EastVNet&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;From the above image, the IP address range for EastVnet can be seen as "10.1.0.0/16" and the subnet IP address range is "10.1.0.0/16".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;For WestVnet&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ar4g5n6vy7k65t6mv98.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ar4g5n6vy7k65t6mv98.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We can use this command &lt;code&gt;az network vnet list --output table&lt;/code&gt; to verify if indeed the virtual networks were created.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;We can add an additional subnet to an existing virtual network through this command.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;az network vnet create --resource-group Proj-RG --name EastVNet --address-prefix 10.2.0.0/16 --subnet-name EastSubnet1 --subnet-prefix 10.2.0.0/24&lt;/code&gt;&lt;br&gt;
This will create an aditional subnet in EastVNet.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use this command to verify the subnets in a particular Vnet. 
&lt;code&gt;az network vnet subnet list --resource-group Proj-RG --vnet-name EastVNet --output table
&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;We can also proceed to the Azure portal to view the virtual networks we created inside the resource group (Proj-RG).&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;STEP 4: Peering the Virtual Networks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The final step is to peer the virtual networks.&lt;br&gt;
We use the following commands to achieve that.&lt;/p&gt;

&lt;p&gt;We begin by creating West to East peering. That is peering WestVNet to EastVNet.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use this command to get the Resource ID of the EastVNet in order to perform the peering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;az network vnet show --resource-group Proj-RG --name EastVNet --query id --out tsv&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;We now run this command for the peering using the EastVNet resource ID. &lt;/li&gt;
&lt;/ul&gt;

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

az network vnet peering create --name WesttoEastPeering --resource-group Proj-RG --vnet-name WestVNet --remote-vnet "/subscriptions/21b8f625-edc8-4cf1-99ec-861cfa81ecc3/resourceGroups/Proj-RG/providers/Microsoft.Network/virtualNetworks/EastVNet" --allow-vnet-access


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

&lt;/div&gt;

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

&lt;p&gt;&lt;strong&gt;Output of the command&lt;/strong&gt;&lt;/p&gt;

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

{
  "allowForwardedTraffic": false,
  "allowGatewayTransit": false,
  "allowVirtualNetworkAccess": true,
  "doNotVerifyRemoteGateways": false,
  "etag": "W/\"2a34dc33-8176-4ad6-ab9f-db94281f712f\"",
  "id": "/subscriptions/21b8f625-edc8-4cf1-99ec-861cfa81ecc3/resourceGroups/Proj-RG/providers/Microsoft.Network/virtualNetworks/WestVNet/virtualNetworkPeerings/WesttoEastPeering",
  "name": "WesttoEastPeering",
  "peeringState": "Initiated",
  "peeringSyncLevel": "RemoteNotInSync",
  "provisioningState": "Succeeded",
  "remoteAddressSpace": {
    "addressPrefixes": [
      "10.1.0.0/16"
    ]
  },
  "remoteVirtualNetwork": {
    "id": "/subscriptions/21b8f625-edc8-4cf1-99ec-861cfa81ecc3/resourceGroups/Proj-RG/providers/Microsoft.Network/virtualNetworks/EastVNet",
    "resourceGroup": "Proj-RG"
  },
  "remoteVirtualNetworkAddressSpace": {
    "addressPrefixes": [
      "10.1.0.0/16"
    ]
  },
  "resourceGroup": "Proj-RG",
  "resourceGuid": "ed95c55f-94b2-0ecd-2f52-dc24ce394fb7",
  "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings",
  "useRemoteGateways": false
}


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;To peer East to West. That is peering EastVNet to WestVNet, in order to complete the peering circuit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We run this command again for the WestVNet to get the &lt;strong&gt;resource ID&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;az network vnet show --resource-group Proj-RG --name WestVNet --query id --out tsv&lt;/code&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;We can now go ahead and run the peering command to peer EastVnet to WestVnet.&lt;/li&gt;
&lt;/ul&gt;

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

az network vnet peering create --name EasttoWestPeering --resource-group Proj-RG --vnet-name EastVNet --remote-vnet "/subscriptions/21b8f625-edc8-4cf1-99ec-861cfa81ecc3/resourceGroups/Proj-RG/providers/Microsoft.Network/virtualNetworks/WestVNet" --allow-vnet-access


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

&lt;/div&gt;

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

&lt;p&gt;&lt;strong&gt;Output of the command&lt;/strong&gt;&lt;/p&gt;

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

{
  "allowForwardedTraffic": false,
  "allowGatewayTransit": false,
  "allowVirtualNetworkAccess": true,
  "doNotVerifyRemoteGateways": false,
  "etag": "W/\"32bb25f8-1dc4-4b74-af18-4a183d67f138\"",
  "id": "/subscriptions/21b8f625-edc8-4cf1-99ec-861cfa81ecc3/resourceGroups/Proj-RG/providers/Microsoft.Network/virtualNetworks/EastVNet/virtualNetworkPeerings/EasttoWestPeering",
  "name": "EasttoWestPeering",
  "peeringState": "Connected",
  "peeringSyncLevel": "FullyInSync",
  "provisioningState": "Succeeded",
  "remoteAddressSpace": {
    "addressPrefixes": [
      "10.2.0.0/16"
    ]
  },
  "remoteVirtualNetwork": {
    "id": "/subscriptions/21b8f625-edc8-4cf1-99ec-861cfa81ecc3/resourceGroups/Proj-RG/providers/Microsoft.Network/virtualNetworks/WestVNet",
    "resourceGroup": "Proj-RG"
  },
  "remoteVirtualNetworkAddressSpace": {
    "addressPrefixes": [
      "10.2.0.0/16"
    ]
  },
  "resourceGroup": "Proj-RG",
  "resourceGuid": "ed95c55f-94b2-0ecd-2f52-dc24ce394fb7",
  "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings",
  "useRemoteGateways": false
}


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;We can use this command to verify the state of peering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;az network vnet peering list --resource-group Proj-RG --vnet-name EastVNet --output table&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SOME BENEFITS OF VIRTUAL NETWORK PEERING&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private Network Communication:&lt;/strong&gt; VNet peering enables private, low-latency communication between VNets, creating a virtual private network within Azure. This is especially useful when you want to keep network traffic within the Azure backbone, avoiding exposure to the public internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simplified Network Architecture:&lt;/strong&gt; VNets that are peered together can communicate as if they are part of a single network. This simplifies network architecture and eliminates the need for complex and costly VPN gateways or dedicated network appliances.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost-Efficiency:&lt;/strong&gt; VNet peering is a cost-effective solution for interconnecting VNets. You don't incur data transfer charges within the same Azure region when using VNet peering, which can lead to significant cost savings compared to data transfer over a VPN or ExpressRoute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Low Latency:&lt;/strong&gt; Since VNet peering traffic stays within Azure's high-speed backbone network, it results in low-latency communication, making it suitable for applications that require quick response times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transitive Connectivity:&lt;/strong&gt; VNet peering can be transitive, allowing traffic to flow from one VNet to another through a peered VNet. This can simplify network topologies and routing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redundancy and High Availability:&lt;/strong&gt; By peering VNets in different regions, you can create redundant network architectures for disaster recovery and high availability scenarios.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isolation:&lt;/strong&gt; Even when VNets are peered, they maintain their isolation. You can apply network security group (NSG) rules and route traffic as needed, providing granular control over communication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hybrid Scenarios:&lt;/strong&gt; VNet peering can be used in hybrid scenarios where you connect on-premises networks to Azure VNets, making it easier to extend your network infrastructure to the cloud.&lt;/p&gt;

&lt;p&gt;In conclusion, mastering the art of Azure VNet peering with Azure CLI opens up a world of possibilities for creating efficient, secure, and interconnected cloud architectures. By following the step-by-step instructions in this guide, you'll be well on your way to harnessing the full potential of Azure's networking capabilities. So, don't hesitate to dive in, explore, and embrace the power of Azure VNet peering to enhance your cloud infrastructure and take your applications to the next level. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thank you for your time and happy peering!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>cloud</category>
      <category>beginners</category>
    </item>
    <item>
      <title>A Step-by-Step Guide on Creating a Resource Group, Virtual Network and Subnet in Azure with Terraform.</title>
      <dc:creator>Bernard Chika Uwaezuoke</dc:creator>
      <pubDate>Wed, 23 Aug 2023 01:36:42 +0000</pubDate>
      <link>https://dev.to/donhadley22/a-step-by-step-guide-on-creating-a-resource-group-virtual-network-and-subnet-in-azure-with-terraform-4b7c</link>
      <guid>https://dev.to/donhadley22/a-step-by-step-guide-on-creating-a-resource-group-virtual-network-and-subnet-in-azure-with-terraform-4b7c</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Welcome to this in-depth blog series where we unravel the art of provisioning &lt;strong&gt;Azure&lt;/strong&gt; resources using &lt;strong&gt;Terraform&lt;/strong&gt;, a powerful Infrastructure as Code (IaC) tool. In this digital era, orchestrating cloud infrastructure efficiently is paramount, and &lt;strong&gt;Terraform&lt;/strong&gt; provides the brush strokes you need to paint your Azure canvas with precision. This article will guide you through the process of creating a &lt;strong&gt;resource group&lt;/strong&gt;, a &lt;strong&gt;virtual network&lt;/strong&gt;, and a &lt;strong&gt;subnet&lt;/strong&gt; in Azure using Terraform, equipping you with the skills to architect and manage cloud resources seamlessly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Terraform?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Terraform is an &lt;strong&gt;open-source&lt;/strong&gt; infrastructure as code (IaC) tool developed by &lt;strong&gt;HashiCorp&lt;/strong&gt;. It allows you to define, manage, and provision infrastructure resources in a declarative way using configuration files. These configuration files, written in &lt;strong&gt;HashiCorp Configuration Language&lt;/strong&gt; (HCL), describe the desired state of your infrastructure components such as virtual machines, networks, storage, and other cloud services.&lt;/p&gt;

&lt;p&gt;Terraform provides a way to manage infrastructure across various cloud providers such as AWS, Azure, Google Cloud, Oracle etc. and even on-premises environments. It helps automate the process of creating, updating, and destroying resources by treating infrastructure as code, enabling you to version-control your infrastructure configurations and collaborate more effectively with your team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some Terraform key concepts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Providers:&lt;/strong&gt; These are plugins that interface with various cloud providers' APIs. Each provider offers resource types that Terraform can manage, such as virtual machines, networks, databases, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Resources:&lt;/strong&gt; These are the fundamental building blocks of your infrastructure, representing the individual components you want to manage. For example, an &lt;strong&gt;AWS instance&lt;/strong&gt; or an &lt;strong&gt;Azure virtual machine&lt;/strong&gt; might be represented as a resource.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Modules:&lt;/strong&gt; Modules allow you to encapsulate and reuse configurations. They can be thought of as reusable blueprints for creating infrastructure components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. State:&lt;/strong&gt; Terraform maintains a state file that keeps track of the current state of your infrastructure as defined in your configuration files. This state file is used to determine the difference between the desired state and the actual state and to perform updates accordingly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Execution Plan:&lt;/strong&gt; Terraform generates an &lt;strong&gt;execution plan&lt;/strong&gt; before making any changes to your infrastructure. This plan outlines what actions Terraform will take to reach the desired state, allowing you to review and approve changes before they are applied.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6.Apply:&lt;/strong&gt; The "&lt;strong&gt;terraform apply&lt;/strong&gt;" command is used to apply the changes defined in your configuration files to your infrastructure. It provisions or updates resources based on the execution plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install VScode (IDE)&lt;/li&gt;
&lt;li&gt;Install Terraform&lt;/li&gt;
&lt;li&gt;Azure Account&lt;/li&gt;
&lt;li&gt;Install WSL Ubuntu&lt;/li&gt;
&lt;li&gt;Azure CLI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's dive in now!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Download and install Visual Studio Code&lt;/strong&gt;&lt;br&gt;
Use the link bellow to download and install Microsoft VScode.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://code.visualstudio.com/download&lt;/code&gt;&lt;br&gt;
&lt;em&gt;Download MS Visual Studio Code – VSCode&lt;/em&gt;&lt;/p&gt;

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

&lt;h2&gt;
  
  
  - Select the suitable one for your operating system. We are selecting &lt;strong&gt;Windows&lt;/strong&gt; for this article. 
&lt;/h2&gt;

&lt;p&gt;Click &lt;strong&gt;Install&lt;/strong&gt;/&lt;strong&gt;Launch&lt;/strong&gt;/&lt;br&gt;
Sign in to MS Visual Studio with your Microsoft Account To create a password for the default user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure Visual Studio to run Ubuntu terminal&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the &lt;strong&gt;VScode&lt;/strong&gt; and click on the extension icon as shown below.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Type &lt;strong&gt;remote wsl&lt;/strong&gt; in the search bar select and then install.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Install WSL Ubuntu from Microsoft Store&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Search for Microsoft Store on your system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In Microsoft Store, Search for, download, and INSTALL Ubuntu&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unpack the App.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Install Terraform in your VScode&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use the documentation in the link below to Install Terraform&lt;br&gt;
 &lt;code&gt;https://cloudlinuxtech.com/install-terraform-on-ubuntu-uninstall-terraform/&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copy and paste these commands, one line at a time:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -

sudo apt-add-repository "deb [arch=$(dpkg --print-architecture)] https://apt.releases.hashicorp.com
$(lsb_release -cs) main"

$sudo apt update
$sudo apt install terraform
$ terraform --version
$ which terraform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Click on the &lt;strong&gt;extension&lt;/strong&gt; icon by the left of your VScode search for and install these extensions indicated in the image below:&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Configure Provider (Azure)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Now download and install &lt;strong&gt;azure-cli&lt;/strong&gt; for ubuntu with these commands one after the other:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update
sudo curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash 
az --version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Now that we are done setting-up our working environment, let's now commence the main business of deploying our resources using terraform.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploying our resources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create a folder on our desktop named it &lt;strong&gt;AZURE TERRAFORM1&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open the folder using VScode&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create two files named &lt;strong&gt;provider.tf&lt;/strong&gt; and &lt;strong&gt;main.tf&lt;/strong&gt; respectively.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Click on the &lt;strong&gt;terminal&lt;/strong&gt; button on the top of the VScode to open your interface below and select the &lt;strong&gt;wsl ubuntu&lt;/strong&gt; to display your &lt;strong&gt;shell prompt&lt;/strong&gt;in order to be able to run your &lt;strong&gt;Linux&lt;/strong&gt; commands.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Go to the &lt;strong&gt;Terraform registry for Azure&lt;/strong&gt; on the link below to get your templates for deploying Azure resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the &lt;strong&gt;provider.tf&lt;/strong&gt; file, copy the provider configuration from the terraform registry and paste.
Then go to the terminal and initialize terraform by running this command.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;terraform init&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;You will get this notification indicating that the initialization was successful.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Login to Azure by running the &lt;code&gt;az login&lt;/code&gt; command.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This will open a &lt;strong&gt;browser tab&lt;/strong&gt; for you to provide your login credentials. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select the appropriate account and sign-in.
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faknxk97xni1c21vjubvs.png" alt="Image description"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The notification below will be displayed on the browser indicating that your login was successful. &lt;/p&gt;

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

&lt;p&gt;The &lt;strong&gt;account&lt;/strong&gt; and &lt;strong&gt;subscription&lt;/strong&gt; details will be displayed in your terminal.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Go to the &lt;strong&gt;main.tf&lt;/strong&gt; file and start deploying your resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Provisioning a Resource Group&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to the Terraform Azure registry and copy a template for &lt;strong&gt;Resource group&lt;/strong&gt; by typing the &lt;strong&gt;resource group&lt;/strong&gt; in the search bar. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Customize to your preference by editing the &lt;strong&gt;values&lt;/strong&gt; of the parameters &lt;strong&gt;resource&lt;/strong&gt;, &lt;strong&gt;name&lt;/strong&gt; and &lt;strong&gt;location&lt;/strong&gt; to suite your specification. The &lt;strong&gt;unique id&lt;/strong&gt; is the element in the quotation mark after the resource. &lt;br&gt;
In this instance, the resource is &lt;strong&gt;"azurerm_resource_group"&lt;/strong&gt; we specified the unique id as &lt;strong&gt;Proj-rg&lt;/strong&gt;. The name is &lt;strong&gt;project1-RG&lt;/strong&gt; and the location is &lt;strong&gt;east us&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;When we are done defining our resource in &lt;strong&gt;HashiCorp Configuration Language&lt;/strong&gt;, we run the &lt;code&gt;terraform init&lt;/code&gt; command again to start the process of provisioning. &lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Then &lt;code&gt;terraform plan&lt;/code&gt; command, to ascertain that what we intend doing is indeed what terraform will do. &lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;We can now run the &lt;code&gt;terraform apply&lt;/code&gt; command to deploy the resource.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;We will be prompted by &lt;strong&gt;terraform&lt;/strong&gt; if we want to go ahead with the provisioning, and we type &lt;strong&gt;yes&lt;/strong&gt; to approve.&lt;/p&gt;

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

&lt;p&gt;Terraform will deploy our resource for us as specified. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;View the provisioned resource in *&lt;em&gt;Azure portal
*&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;To Provision a Virtual Network&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now we want to create a &lt;strong&gt;Virtual Network&lt;/strong&gt; within the resource group we just created.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to the &lt;strong&gt;Terraform Registry for Azure&lt;/strong&gt; and copy the template for provisioning a virtual network and paste it right below the resource group code in the &lt;strong&gt;main.tf&lt;/strong&gt; file in our code editor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Edit it to our specification as shown in the image below. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;When we are done editing to our specification, by providing the unique id &lt;strong&gt;vnet1&lt;/strong&gt;, name &lt;strong&gt;project1-network&lt;/strong&gt;, resource group &lt;strong&gt;Proj-rg&lt;/strong&gt;, location specified with the resource group unique id and the private IPv4 range &lt;strong&gt;10.0.0.0/16&lt;/strong&gt;. We conslude the process by running these same three commands one after the other to deploy the virtual network to Azure cloud.
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;View our Virtual Network in Azure Portal.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Provisioning a subnet in our Virtual network&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We begin by going to the terraform library again to search for &lt;strong&gt;subnet&lt;/strong&gt; and copy the desired template and place it right below the virtual network code on our editor. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We continue by providing our customized value for the parameters in the template as shown in the image below.&lt;br&gt;
In this instance, the unique id for our subnet is &lt;strong&gt;example&lt;/strong&gt; and the name is &lt;strong&gt;example-subnet&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We provide the resource group and virtual network parameters values using there unique &lt;strong&gt;Proj-rg&lt;/strong&gt; and &lt;strong&gt;vnet1&lt;/strong&gt; respectively. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We also provide the IPv4 address range for the subnet &lt;strong&gt;10.0.1.0/24&lt;/strong&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;We conclude the process by running the terraform commands one after the other again.
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;We view our subnet in Azure portal.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;We can also see the &lt;strong&gt;address range&lt;/strong&gt; of the virtual network the resource group in the Azure portal.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Below is our module, which we can push to Github for future us and to be used by others.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Destroying our resources&lt;/strong&gt;&lt;br&gt;
As convention demands, we clean-up our cloud account by deleting resources that are no longer in use. &lt;br&gt;
To destroy our resources, using terraform, we just run the command &lt;code&gt;terraform destroy&lt;/code&gt; on out terminal and type &lt;strong&gt;yes&lt;/strong&gt; when prompted or add the argument &lt;code&gt;--auto-approve&lt;/code&gt; to approve the removal automatically.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2fozesmvanklam9z802z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2fozesmvanklam9z802z.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
From the above images, we can see that the resources we provisioned have been destroyed.&lt;/p&gt;

&lt;p&gt;With this, we come to this guide on using terraform for Azure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
In this step-by-step guide, we've embarked on a journey into the world of cloud infrastructure provisioning with &lt;strong&gt;Terraform&lt;/strong&gt; on the &lt;strong&gt;Microsoft Azure&lt;/strong&gt; platform. We've covered the fundamentals, walked through the essential setup, and explored how to create and manage resources in a structured and efficient manner.&lt;/p&gt;

&lt;p&gt;As you venture forward, consider exploring advanced Terraform topics such as &lt;strong&gt;managing state&lt;/strong&gt;, &lt;strong&gt;handling secrets&lt;/strong&gt;, and **integrating **with other tools in the DevOps ecosystem. Remember, learning is a continuous journey, and each step you take deepens your mastery.&lt;/p&gt;

&lt;p&gt;Thank you for joining us on this exploration of Terraform and Azure. Please, feel free to share your thoughts and questions in the comments section, and remember, to subscribe and follow us for more articles like this.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>azure</category>
      <category>vscode</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
